diff --git a/binaries/data/mods/public/globalscripts/interpolation.js b/binaries/data/mods/public/globalscripts/interpolation.js index a43d2e2261..111a3d1e5c 100644 --- a/binaries/data/mods/public/globalscripts/interpolation.js +++ b/binaries/data/mods/public/globalscripts/interpolation.js @@ -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)); } diff --git a/binaries/data/mods/public/maps/random/rmgen/painter.js b/binaries/data/mods/public/maps/random/rmgen/painter.js index 3e9b35a0e1..cd3132e2e1 100644 --- a/binaries/data/mods/public/maps/random/rmgen/painter.js +++ b/binaries/data/mods/public/maps/random/rmgen/painter.js @@ -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])))); + } +}; diff --git a/binaries/data/mods/public/maps/random/rmgen/random_map.js b/binaries/data/mods/public/maps/random/rmgen/random_map.js index ebb99deb58..da3fc3230a 100644 --- a/binaries/data/mods/public/maps/random/rmgen/random_map.js +++ b/binaries/data/mods/public/maps/random/rmgen/random_map.js @@ -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. diff --git a/binaries/data/mods/public/maps/random/rmgen2/gaia.js b/binaries/data/mods/public/maps/random/rmgen2/gaia.js index e18ab28d47..61e441a66e 100644 --- a/binaries/data/mods/public/maps/random/rmgen2/gaia.js +++ b/binaries/data/mods/public/maps/random/rmgen2/gaia.js @@ -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; -}