Implement JS random map library counterpart to 2180862d40 and 204b04f2d4, refs #4816, #5018.

Refactors the heightmap and terrainmap copying code from 33e3e6c2ab and
the bicubic interpolation code from 64b1da0ddf / 93aefe0787 which were
unused since f5375cbcb9.

This was SVN commit r21133.
This commit is contained in:
elexis
2018-02-07 02:08:20 +00:00
parent c0b493bdc4
commit 6319647795
4 changed files with 162 additions and 69 deletions
@@ -19,11 +19,11 @@ function cubicInterpolation(tension, x, p0, p1, p2, p3)
/**
* Two dimensional interpolation within a square grid using a polynomial of degree three.
*
* @param {Number} x, y - Location of the point to interpolate, relative to p11
* @param {Vector2D} position - Location of the point to interpolate, relative to p11
*/
function bicubicInterpolation
(
x, y,
position,
p00, p01, p02, p03,
p10, p11, p12, p13,
p20, p21, p22, p23,
@@ -33,9 +33,9 @@ function bicubicInterpolation
let tension = 0.5;
return cubicInterpolation(
tension,
x,
cubicInterpolation(tension, y, p00, p01, p02, p03),
cubicInterpolation(tension, y, p10, p11, p12, p13),
cubicInterpolation(tension, y, p20, p21, p22, p23),
cubicInterpolation(tension, y, p30, p31, p32, p33));
position.x,
cubicInterpolation(tension, position.y, p00, p01, p02, p03),
cubicInterpolation(tension, position.y, p10, p11, p12, p13),
cubicInterpolation(tension, position.y, p20, p21, p22, p23),
cubicInterpolation(tension, position.y, p30, p31, p32, p33));
}
@@ -325,3 +325,116 @@ function breadthFirstSearchPaint(args)
}
}
}
/**
* Paints the given texture-mapping to the given tiles.
*
* @param {String[]} textureIDs - Names of the terrain textures
* @param {Number[]} textureNames - One-dimensional array of indices of texturenames, one for each tile of the entire map.
* @returns
*/
function TerrainTextureArrayPainter(textureIDs, textureNames)
{
this.textureIDs = textureIDs;
this.textureNames = textureNames;
};
TerrainTextureArrayPainter.prototype.paint = function(area)
{
let sourceSize = Math.sqrt(this.textureIDs.length);
let scale = sourceSize / g_Map.getSize();
for (let point of area.points)
{
let sourcePos = Vector2D.mult(point, scale).floor();
g_Map.setTexture(point, this.textureNames[this.textureIDs[sourcePos.x * sourceSize + sourcePos.y]]);
}
};
/**
* Copies the given heightmap to the given area.
* Scales the horizontal plane proportionally and optionally uses bicubic interpolation.
* The heightrange is either scaled proportionally or mapped to the given heightrange.
*
* @param {Uint16Array} heightmap - One dimensional array of vertex heights.
* @param {Number} [normalMinHeight] - The minimum height the elevation grid of 320 tiles would have.
* @param {Number} [normalMaxHeight] - The maximum height the elevation grid of 320 tiles would have.
*/
function HeightmapPainter(heightmap, bicubicInterpolation, normalMinHeight = undefined, normalMaxHeight = undefined)
{
this.heightmap = heightmap;
this.bicubicInterpolation = bicubicInterpolation;
this.verticesPerSide = Math.sqrt(heightmap.length);
this.normalMinHeight = normalMinHeight;
this.normalMaxHeight = normalMaxHeight;
};
HeightmapPainter.prototype.paint = function(area)
{
if (this.bicubicInterpolation)
this.paintBicubic(area);
else
this.paintNearest(area);
};
HeightmapPainter.prototype.getScale = function()
{
return this.verticesPerSide / (g_Map.getSize() + 1);
};
HeightmapPainter.prototype.scaleHeight = function(height)
{
if (this.normalMinHeight === undefined || this.normalMaxHeight === undefined)
return height / this.getScale() / HEIGHT_UNITS_PER_METRE;
let minHeight = this.normalMinHeight * (g_Map.getSize() + 1) / 321;
let maxHeight = this.normalMaxHeight * (g_Map.getSize() + 1) / 321;
return minHeight + (maxHeight - minHeight) * height / 0xFFFF;
};
HeightmapPainter.prototype.paintNearest = function(area)
{
let scale = this.getScale();
for (let point of area.points)
{
let sourcePos = Vector2D.mult(point, scale).floor();
g_Map.setHeight(point, scaleHeight(this.heightmap[sourcePos.y * this.verticesPerSide + sourcePos.x]));
}
};
HeightmapPainter.prototype.paintBicubic = function(area)
{
let scale = this.getScale();
let leftBottom = new Vector2D(0, 0);
let rightTop = new Vector2D(this.verticesPerSide, this.verticesPerSide);
let brushSize = new Vector2D(3, 3);
let brushCenter = new Vector2D(1, 1);
// Additional complexity to process all 4 vertices of each tile, i.e the last row too
let seen = new Array(g_Map.getSize() + 1).fill(0).map(zero => new Uint8Array(g_Map.getSize() + 1).fill(false));
for (let point of area.points)
for (let vertex of g_TileVertices)
{
let vertexPos = Vector2D.add(point, vertex);
if (seen[vertexPos.x][vertexPos.y])
continue;
seen[vertexPos.x][vertexPos.y] = true;
let sourcePos = Vector2D.mult(vertexPos, scale);
let sourceTilePos = sourcePos.clone().floor();
let brushPosition = Vector2D.max(
leftBottom,
Vector2D.min(
Vector2D.sub(sourceTilePos, brushCenter),
Vector2D.sub(rightTop, brushSize).sub(brushCenter)));
g_Map.setHeight(vertexPos, bicubicInterpolation(
Vector2D.sub(sourcePos, brushPosition).sub(brushCenter),
...getPointsInBoundingBox(getBoundingBox([brushPosition, Vector2D.add(brushPosition, brushSize)])).map(pos =>
this.scaleHeight(this.heightmap[pos.y * this.verticesPerSide + pos.x]))));
}
};
@@ -61,11 +61,53 @@ function RandomMap(baseHeight, baseTerrain)
this.entityCount = 150;
}
/**
* Prints a timed log entry to stdout and the logfile.
*/
RandomMap.prototype.log = function(text)
{
this.logger.print(text);
};
/**
* Loads an imagefile and uses it as the heightmap for the current map.
* Scales the map (including height) proportionally with the mapsize.
*/
RandomMap.prototype.LoadMapTerrain = function(filename)
{
g_Map.log("Loading terrain file " + filename);
let mapTerrain = Engine.LoadMapTerrain("maps/random/" + filename + ".pmp");
let heightmapPainter = new HeightmapPainter(mapTerrain.height, true);
createArea(
new MapBoundsPlacer(),
[
heightmapPainter,
new TerrainTextureArrayPainter(mapTerrain.textureIDs, mapTerrain.textureNames)
]);
return heightmapPainter.getScale();
};
/**
* Loads PMP terrain file that contains elevation grid and terrain textures created in atlas.
* Scales the map (including height) proportionally with the mapsize.
* Notice that the image heights can only be between 0 and 255, but the resulting sizes can exceed that range due to the cubic interpolation.
*/
RandomMap.prototype.LoadHeightmapImage = function(filename, normalMinHeight, normalMaxHeight)
{
g_Map.log("Loading heightmap " + filename);
let heightmapPainter = new HeightmapPainter(Engine.LoadHeightmapImage("maps/random/" + filename), true, normalMinHeight, normalMaxHeight);
createArea(
new MapBoundsPlacer(),
heightmapPainter);
return heightmapPainter.getScale();
};
/**
* Returns the ID of a texture name.
* Creates a new ID if there isn't one assigned yet.
@@ -1207,65 +1207,3 @@ function getRandomDeviation(base, deviation)
{
return base + randFloat(-1, 1) * Math.min(base, deviation);
}
/**
* Import a given digital elevation model.
* Scale it to the mapsize and paint the textures specified by coordinate on it.
*
* @return the ratio of heightmap tiles per map size tiles
*/
function paintHeightmap(mapName, func = undefined)
{
/**
* @property heightmap - An array with a square number of heights.
* @property tilemap - The IDs of the palletmap to be painted for each heightmap tile.
* @property pallet - The tile texture names used by the tilemap.
*/
let mapData = Engine.ReadJSONFile("maps/random/" + mapName + ".hmap");
let mapSize = g_Map.getSize(); // Width of the map in terrain tiles
let hmSize = Math.sqrt(mapData.heightmap.length);
let scale = hmSize / (mapSize + 1); // There are mapSize + 1 vertices (each 1 tile is surrounded by 2x2 vertices)
for (let x = 0; x <= mapSize; ++x)
for (let y = 0; y <= mapSize; ++y)
{
let position = new Vector2D(x, y);
let hmPoint = Vector2D.mult(position, scale);
let hmTile = new Vector2D(Math.floor(hmPoint.x), Math.floor(hmPoint.y));
let shift = new Vector2D(0, 0);
if (hmTile.x == 0)
shift.x = 1;
else if (hmTile.x == hmSize - 1)
shift.x = - 2;
else if (hmTile.x == hmSize - 2)
shift.x = - 1;
if (hmTile.y == 0)
shift.y = 1;
else if (hmTile.y == hmSize - 1)
shift.y = - 2;
else if (hmTile.y == hmSize - 2)
shift.y = - 1;
let neighbors = [];
for (let localXi = 0; localXi < 4; ++localXi)
for (let localYi = 0; localYi < 4; ++localYi)
neighbors.push(mapData.heightmap[(hmTile.x + localXi + shift.x - 1) * hmSize + (hmTile.y + localYi + shift.y - 1)]);
g_Map.setHeight(position, bicubicInterpolation(hmPoint.x - hmTile.x - shift.x, hmPoint.y - hmTile.y - shift.y, ...neighbors) / scale);
if (x < mapSize && y < mapSize)
{
let i = hmTile.x * hmSize + hmTile.y;
let tile = mapData.pallet[mapData.tilemap[i]];
createTerrain(tile).place(position);
if (func)
func(tile, x, y);
}
}
return scale;
}