我已经实现了Diamond-Square函数,该函数会生成一个高度图。乍一看,该实现似乎正常工作。
这些只是两个示例,但是已经可以看到输出值看起来总体上很高。只有很少的真正黑暗的价值。一世。 E.如果您查看此paper中的高度图(由菱形方块生成),您会发现它们不像我的高度图那样均匀。不同区域之间存在更多的偏移。有些地区看起来像火山口。
我无法确定此行为的原因是错误的参数化或实现。尽管网络上的示例实现确实有所不同,但我认为我已经掌握了基本思路。
我正在处理平面类型的数组。我传递给函数的参数是:
sideLength
maxHeight
roughness
我正在调用Heightmap
函数以获取输出:
/**
* Creates a heightmap based on parameters passed.
* @param {number} sideLength - Side length of a the resulting grid array. Diamond-Square can only have a size (2^n)+1.
* @param {number} maxHeight - Max height value for the heightmap's values.
* @param {number} roughness - A factor which is used as offset value for the heightmap. Defines the roughness of a heightmap.
* @returns {Float32Array} - A flat `Float32Array` representing a 2D-grid with size `sideLength * sideLength`.
*/
static HeightMap(sideLength, maxHeight, roughness) {
const n = Math.log(sideLength - 1) / Math.log(2);
if (n < 0 || n % 1 != 0) {
throw "Invalid side length in Diamond Square: Side Length has to be in range of `(2^n) + 1`.";
}
let gridArray = new Float32Array(sideLength * sideLength);
this._initGrid(gridArray, sideLength, maxHeight);
this._seed(gridArray, sideLength, roughness);
return gridArray;
}
这里首先启动“网格”:
/**
* Sets the initial corner values for a Diamond-Square grid.
* @param {Float32Array} gridArray - An `Float32Array` with its values (ideally) set to `0`.
* @param {number} sideLength - Side length of a the resulting grid array. Diamond-Square can only have a size `(2^n)+1`.
* @param {number} maxHeight - Max height value for the heightmap's values.
* @returns {Float32Array} - A flat `Float32Array` representing a 2D-grid with its NW, NE, SE and SW values initialized.
*/
static _initGrid(gridArray, sideLength, maxHeight) {
gridArray[0] = MathHelper.RandomInt(0, maxHeight); // NW
gridArray[sideLength - 1] = MathHelper.RandomInt(0, maxHeight); // NE
gridArray[sideLength * sideLength - 1] = MathHelper.RandomInt(0, maxHeight); // SE
gridArray[sideLength * sideLength - sideLength] = MathHelper.RandomInt(0, maxHeight); // SW
return gridArray;
}
随后,HeightMap
函数将调用_seed
,这基本上是Diamond-Square循环:
/**
* Performs the Diamond Square (aka. Midpoint displacement) algorithm on a given flat TypedArray.
* @param {Float32Array} gridArray - An (Diamond-Square-initialized) `Float32Array`.
* @param {number} sideLength - Side length of a the resulting grid array.
* @param {number} roughness - A factor which is used as offset value for the heightmap. Defines the roughness of a heightmap.
* @returns {Float32Array} - Returns a ready to use heightmap produced by the Diamond-Square algorithm.
*/
static _seed(gridArray, sideLength, roughness) {
let step = Math.sqrt(gridArray.length) - 1;
let size = Math.sqrt(gridArray.length) - 1;
let currentRoughness = roughness;
while (step / 2 >= 1) {
let numSquares = (Math.pow(size, 2)) / (Math.pow(step, 2));
let perRowSquares = Math.floor(Math.sqrt(numSquares));
for (let i = 0; i < perRowSquares; i++) {
for (let j = 0; j < perRowSquares; j++) {
const nwIndex = this._getNWIndex(i, j, step, sideLength);
const cornerValues = this._getCornerValues(nwIndex, gridArray, sideLength, step);
this._diamondStep(nwIndex, cornerValues, gridArray, sideLength, step, currentRoughness);
this._squareStep(nwIndex, cornerValues, gridArray, sideLength, step, currentRoughness);
}
}
currentRoughness /= 2.0;
step /= 2;
}
return gridArray;
}
注意我正在基于当前西北指标的索引来计算位置指标。为此,我有一个功能:
/**
* Returns the array index for the north-west value for the current step.
* @param {number} i - Current row, I guess.
* @param {number} j - Current column, I guess.
* @param {number} stepSize - Current step size.
* @param {number} sideLength - Grid's side length.
* @returns {number} - Returns the index for current north-west value.
*/
static _getNWIndex(i, j, stepSize, sideLength) {
return (i * (stepSize * sideLength)) + j * stepSize;
}
因为在菱形中使用了所有四个角值,并且在平方步中我也有一个功能:
/**
* Return an array holding the north-west, north-east, south-west and south-east values for the current step.
* @param {number} nwIndex - North-West index for current step.
* @param {Float32Array} gridArray - The corner values for the current step.
* @param {number} sideLength - Grid's side length.
* @param {number} stepSize - Current step size.
* @returns {Float32Array} - Returns the typed array the function of operating on.
*/
static _getCornerValues(nwIndex, gridArray, sideLength, stepSize) {
return [
gridArray[nwIndex], // NW
gridArray[nwIndex + stepSize], // NE
gridArray[nwIndex + stepSize * sideLength], // SW
gridArray[nwIndex + stepSize + stepSize * sideLength] // SE
];
}
最后但并非最不重要的是,我有_diamondStep
和_sqaureStep
:
/**
* Performs the Diamond Step by setting the center value for the current step.
* @param {number} nwIndex - North-West index for current step.
* @param {number[]} cornerValues - The corner values for the current step.
* @param {Float32Array} gridArray - Array holding heightmap data. Function will write to this array.
* @param {number} sideLength - Grid's side length.
* @param {number} stepSize - Current step size.
* @returns {Float32Array} - Returns the typed array the function of operating on.
*/
static _diamondStep(nwIndex, cornerValues, gridArray, sideLength, stepSize, roughness) {
// Center point. Calculated from "East - `stepSize / 2`"
gridArray[(((nwIndex + stepSize * sideLength) + stepSize) - (stepSize * sideLength) / 2) - stepSize / 2]
= (cornerValues[0] + cornerValues[1] + cornerValues[2] + cornerValues[3]) / 4 + (roughness * MathHelper.RandomInt(-1, 1));
return gridArray;
}
/**
* Performs the Square Step by setting the north, east, south and west values for the current step.
* @param {number} nwIndex - North-West index for current step.
* @param {number[]} cornerValues - The corner values for the current step.
* @param {Float32Array} gridArray - Array holding heightmap data. Function will write to this array.
* @param {number} sideLength - Grid's side length.
* @param {number} stepSize - Current step size.
* @param {number} roughness - Roughness factor for the current step.
* @returns {Float32Array} - Returns the typed array the function of operating on.
*/
static _squareStep(nwIndex, cornerValues, gridArray, sideLength, stepSize, roughness) {
const average = (cornerValues[0] + cornerValues[1] + cornerValues[2] + cornerValues[3]) / 4;
const value = average + (roughness * MathHelper.RandomInt(-1, 1));
// N
gridArray[nwIndex + (stepSize / 2)] = value;
// E
gridArray[((nwIndex + stepSize * sideLength) + stepSize) - (stepSize * sideLength) / 2] = value;
// S
gridArray[(nwIndex + stepSize * sideLength) + stepSize / 2] = value;
// W
gridArray[(nwIndex + stepSize * sideLength) - (stepSize * sideLength) / 2] = value;
return gridArray;
}
正如我之前提到的,实现似乎可行。我还是想知道整体的“白度”是由错误的参数化或错误的实现引起的吗?
这是一个工作的小提琴:
function HeightMap(sideLength, maxHeight, roughness) {
const n = Math.log(sideLength - 1) / Math.log(2);
if (n < 0 || n % 1 != 0) {
throw "Invalid side length in Diamond Square: Side Length has to be in range of `(2^n) + 1`.";
}
let gridArray = new Float32Array(sideLength * sideLength);
_initGrid(gridArray, sideLength, maxHeight);
_seed(gridArray, sideLength, roughness);
return gridArray;
}
function _initGrid(gridArray, sideLength, maxHeight) {
gridArray[0] = RandomInt(0, maxHeight); // NW
gridArray[sideLength - 1] = RandomInt(0, maxHeight); // NE
gridArray[sideLength * sideLength - 1] = RandomInt(0, maxHeight); // SE
gridArray[sideLength * sideLength - sideLength] = RandomInt(0, maxHeight); // SW
return gridArray;
}
function _seed(gridArray, sideLength, roughness) {
let step = Math.sqrt(gridArray.length) - 1;
let size = Math.sqrt(gridArray.length) - 1;
let currentRoughness = roughness;
while (step / 2 >= 1) {
let numSquares = (Math.pow(size, 2)) / (Math.pow(step, 2));
let perRowSquares = Math.floor(Math.sqrt(numSquares));
for (let i = 0; i < perRowSquares; i++) {
for (let j = 0; j < perRowSquares; j++) {
const nwIndex = _getNWIndex(i, j, step, sideLength);
const cornerValues = _getCornerValues(nwIndex, gridArray, sideLength, step);
_diamondStep(nwIndex, cornerValues, gridArray, sideLength, step, currentRoughness);
_squareStep(nwIndex, cornerValues, gridArray, sideLength, step, currentRoughness);
}
}
currentRoughness /= 2.0;
step /= 2;
}
return gridArray;
}
function _diamondStep(nwIndex, cornerValues, gridArray, sideLength, stepSize, roughness) {
gridArray[(((nwIndex + stepSize * sideLength) + stepSize) - (stepSize * sideLength) / 2) - stepSize / 2] =
(cornerValues[0] + cornerValues[1] + cornerValues[2] + cornerValues[3]) / 4 + (roughness * RandomInt(-1, 1));
return gridArray;
}
function _squareStep(nwIndex, cornerValues, gridArray, sideLength, stepSize, roughness) {
const average = (cornerValues[0] + cornerValues[1] + cornerValues[2] + cornerValues[3]) / 4;
const value = average + (roughness * RandomInt(-1, 1));
// N
gridArray[nwIndex + (stepSize / 2)] = value;
// E
gridArray[((nwIndex + stepSize * sideLength) + stepSize) - (stepSize * sideLength) / 2] = value;
// S
gridArray[(nwIndex + stepSize * sideLength) + stepSize / 2] = value;
// W
gridArray[(nwIndex + stepSize * sideLength) - (stepSize * sideLength) / 2] = value;
return gridArray;
}
function _getCornerValues(nwIndex, gridArray, sideLength, stepSize) {
return [
gridArray[nwIndex], // NW
gridArray[nwIndex + stepSize], // NE
gridArray[nwIndex + stepSize * sideLength], // SW
gridArray[nwIndex + stepSize + stepSize * sideLength] // SE
];
}
function _getNWIndex(i, j, stepSize, sideLength) {
return (i * (stepSize * sideLength)) + j * stepSize;
}
function GenerateIterations(max) {
let iterations = [];
for (let n = 0; n < max; n++) {
iterations.push(Math.pow(2, n) + 1);
}
return iterations;
}
function Grayscale(canvasName, data, rows, cols) {
let canvas = document.getElementById(canvasName);
let ctx = canvas.getContext("2d");
let imageData = ctx.createImageData(cols, rows);
for (let i = 0; i < data.length; i++) {
const color = data[i];
imageData.data[i * 4] = color;
imageData.data[i * 4 + 1] = color;
imageData.data[i * 4 + 2] = color;
imageData.data[i * 4 + 3] = 255;
}
ctx.putImageData(imageData, 0, 0);
}
function RandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
let terrainGrid = HeightMap(257, 255, 50);
Grayscale('grayscaleCanvas', terrainGrid, 257, 257);
.greyscaleCanvas {
border: solid 1px black;
}
<canvas id="grayscaleCanvas" class="greyscaleCanvas" width="257px" height="257px"></canvas>
答案 0 :(得分:3)
因此,基于对算法的工作原理的理解,我对代码进行了一些重新设计。完全有可能仍然无法正常工作,但我认为无论如何您都可以使用此代码。
根据对维基百科上钻石方块步骤的理解,我认为您的算法中也发现了一些问题:
function HeightMap(sideLength, maxHeight, roughness) {
const n = Math.log(sideLength - 1) / Math.log(2);
if (n < 0 || n % 1 != 0) {
throw "Invalid side length in Diamond Square: Side Length has to be in range of `(2^n) + 1`.";
}
let gridArray = new Array(sideLength);
for (var i = 0; i < gridArray.length; i++) {
gridArray[i] = new Float32Array(sideLength);
}
gridArray = _initGrid(gridArray, sideLength, maxHeight);
gridArray = _seed(gridArray, sideLength, roughness);
return gridArray;
}
function _initGrid(gridArray, sideLength, maxHeight) {
gridArray[0][0] = RandomInt(0, maxHeight); // NW
gridArray[0][sideLength-1] = RandomInt(0, maxHeight); // NE
gridArray[sideLength-1][sideLength-1] = RandomInt(0, maxHeight); // SE
gridArray[sideLength-1][0] = RandomInt(0, maxHeight); // SW
return gridArray;
}
function _seed(gridArray, sideLength, roughness) {
let step = sideLength - 1;
let size = sideLength - 1;
let currentRoughness = roughness;
let run_num = 0
while (step / 2 >= 1) {
console.log(run_num)
run_num = run_num + 1
let numSquares = Math.pow(size, 2) / Math.pow(step, 2);
let perRowSquares = Math.floor(Math.sqrt(numSquares));
for (let i = 0; i < perRowSquares; i++) {
for (let j = 0; j < perRowSquares; j++) {
row = i*step
col = j*step
const squareCornerValues = _getSquareCornerValues(gridArray, row, col, step)
gridArray = _diamondStep(squareCornerValues, row, col, step, gridArray, currentRoughness);
gridArray = _squareStep(row, col, step, gridArray, sideLength, currentRoughness)
// _squareStep(diamondMidPoints, gridArray, step, currentRoughness);
}
}
currentRoughness /= 2.0;
step /= 2;
}
return gridArray;
}
function _diamondStep(squareCornerValues, i, j, step, gridArray, currentRoughness) {
gridArray[row+step/2][col+step/2] = (squareCornerValues[0] + squareCornerValues[1] + squareCornerValues[2] + squareCornerValues[3] ) / 4 + (currentRoughness * RandomInt(-1, 1));
return gridArray;
}
function _squareStep(row, col, step, gridArray, sideLength, currentRoughness) {
let diamondMidPoints = [[row, col+step/2], //top
[row+step/2, col], //left
[row+step, col+step/2], //right
[row+step/2, col+step] //bottom
];
for (let z = 0; z < diamondMidPoints.length; z++){
corners = _get_diamond_corners(diamondMidPoints[z], step, sideLength, gridArray);
gridArray[diamondMidPoints[z][0]][diamondMidPoints[z][1]] = (corners[0] + corners[1] + corners[2] + corners[3]) /4 + (currentRoughness * RandomInt(-1, 1));
}
return gridArray;
}
function _getSquareCornerValues(gridArray, row, col, step) {
return [
gridArray[row][col], // NW
gridArray[row][col+step], // NE
gridArray[row+step][col], // SW
gridArray[row+step][col+step] // SE
];
}
function _get_diamond_corners(diamondMidPoints, step, sideLength, gridArray){
row = diamondMidPoints[0];
col = diamondMidPoints[1];
top_coord = [(row - step/2 + sideLength) % sideLength, col];
left_coord = [row, (col - step/2 + sideLength) % sideLength];
right_coord = [row, (col + step/2 + sideLength) % sideLength];
bottom_coord = [(row + step/2 + sideLength) % sideLength, col];
return [gridArray[top_coord[0]][top_coord[1]],
gridArray[left_coord[0]][left_coord[1]],
gridArray[right_coord[0]][right_coord[1]],
gridArray[bottom_coord[0]][bottom_coord[1]]
];
}
function Grayscale(canvasName, data, rows, cols) {
let canvas = document.getElementById(canvasName);
let ctx = canvas.getContext("2d");
let imageData = ctx.createImageData(cols, rows);
for (let i = 0; i < data.length; i++) {
const color = data[i];
imageData.data[i * 4] = color;
imageData.data[i * 4 + 1] = color;
imageData.data[i * 4 + 2] = color;
imageData.data[i * 4 + 3] = 255;
}
ctx.putImageData(imageData, 0, 0);
}
function RandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
let terrainGrid = HeightMap(257, 255, 50);
terrainList = []
for (let q=0; q < 257; q++) {
terrainList.push.apply(terrainList, terrainGrid[q])
}
Grayscale('grayscaleCanvas', terrainList, 257, 257);
我做的第一件事是将其更改为使用二维数组索引。这样可以更轻松地实现平方步长求平均值(我能够更简单地环绕矩阵的边缘)。
我还更改了它,以使用更简单的数组中的实际行和列值,而不是块坐标。
我认为您最初并未正确计算平方步长。您使用的拐角与菱形台阶有关,但是您只是对平方台阶求平均值,而不是根据计算的中点值和拐角的相关子集找到平均值(请参阅Wikipedia菱形正方形图像以了解哪些内容)。我的意思是。)
这里是所有内容的JSFiddle,希望这是您想要的更多东西。 (请注意:如果没有什么习惯性的操作,那是因为我对Java语言的了解不强):https://jsfiddle.net/z6so4xyc/15/