forked from mirrors/0ad
Implement JS random map library counterpart to 2180862d40 and 204b04f2d4, refs #4816, #5018.
Refactors the heightmap and terrainmap copying code from33e3e6c2aband the bicubic interpolation code from64b1da0ddf/93aefe0787which were unused sincef5375cbcb9. This was SVN commit r21133.
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user