Changeset View
Changeset View
Standalone View
Standalone View
ps/trunk/binaries/data/mods/public/maps/random/caledonian_meadows.js
RMS.LoadLibrary("rmgen"); | RMS.LoadLibrary("rmgen"); | ||||
RMS.LoadLibrary("heightmap"); | RMS.LoadLibrary("heightmap"); | ||||
InitMap(); | InitMap(); | ||||
let genStartTime = Date.now(); | let genStartTime = Date.now(); | ||||
/** | /** | ||||
* Returns an approximation of the heights of the tiles between the vertices, a tile centered heightmap | |||||
* A tile centered heightmap is one smaller in width and height than an ordinary heightmap | |||||
* It is meant to e.g. texture a map by height (x/y coordinates correspond to those of the terrain texture map) | |||||
* Don't use this to override g_Map height (Potentially breaks the map)! | |||||
* @param {array} [heightmap=g_Map.height] - A reliefmap the tile centered version should be build from | |||||
*/ | |||||
function getTileCenteredHeightmap(heightmap = g_Map.height) | |||||
{ | |||||
let max_x = heightmap.length - 1; | |||||
let max_y = heightmap[0].length - 1; | |||||
let tchm = []; | |||||
for (let x = 0; x < max_x; ++x) | |||||
{ | |||||
tchm[x] = new Float32Array(max_y); | |||||
for (let y = 0; y < max_y; ++y) | |||||
tchm[x][y] = 0.25 * (heightmap[x][y] + heightmap[x + 1][y] + heightmap[x][y + 1] + heightmap[x + 1][y + 1]); | |||||
} | |||||
return tchm; | |||||
} | |||||
/** | |||||
* Returns an inclination map corresponding to the tiles between the heightmaps vertices: | |||||
* array of heightmap width-1 arrays of height-1 vectors (associative arrays) of the from: | |||||
* {"x": x_slope, "y": y_slope] so a 2D Vector pointing to the hightest incline (with the length the incline in the vectors direction) | |||||
* The x and y coordinates of a tile in the terrain texture map correspond to those of the inclination map | |||||
* @param {array} [heightmap=g_Map.height] - The reliefmap the inclination map is to be generated from | |||||
*/ | |||||
function getInclineMap(heightmap) | |||||
{ | |||||
heightmap = (heightmap || g_Map.height); | |||||
let max_x = heightmap.length - 1; | |||||
let max_y = heightmap[0].length - 1; | |||||
let inclineMap = []; | |||||
for (let x = 0; x < max_x; ++x) | |||||
{ | |||||
inclineMap[x] = []; | |||||
for (let y = 0; y < max_y; ++y) | |||||
{ | |||||
let dx = heightmap[x + 1][y] - heightmap[x][y]; | |||||
let dy = heightmap[x][y + 1] - heightmap[x][y]; | |||||
let next_dx = heightmap[x + 1][y + 1] - heightmap[x][y + 1]; | |||||
let next_dy = heightmap[x + 1][y + 1] - heightmap[x + 1][y]; | |||||
inclineMap[x][y] = {"x": 0.5 * (dx + next_dx), "y": 0.5 * (dy + next_dy)}; | |||||
} | |||||
} | |||||
return inclineMap; | |||||
} | |||||
/** | |||||
* Returns a slope map (same form as the a heightmap with one less width and height) | |||||
* Not normalized. Only returns the steepness (float), not the direction of incline. | |||||
* The x and y coordinates of a tile in the terrain texture map correspond to those of the slope map | |||||
* @param {array} [inclineMap=getInclineMap(g_Map.height)] - A map with the absolute inclination for each tile | |||||
*/ | |||||
function getSlopeMap(inclineMap = getInclineMap(g_Map.height)) | |||||
{ | |||||
let max_x = inclineMap.length; | |||||
let slopeMap = []; | |||||
for (let x = 0; x < max_x; ++x) | |||||
{ | |||||
let max_y = inclineMap[x].length; | |||||
slopeMap[x] = new Float32Array(max_y); | |||||
for (let y = 0; y < max_y; ++y) | |||||
slopeMap[x][y] = Math.pow(inclineMap[x][y].x * inclineMap[x][y].x + inclineMap[x][y].y * inclineMap[x][y].y, 0.5); | |||||
} | |||||
return slopeMap; | |||||
} | |||||
/** | |||||
* Returns the order to go through the points for the shortest closed path (array of indices) | |||||
* @param {array} [points] - Points to be sorted of the form {"x": x_value, "y": y_value} | |||||
*/ | |||||
function getOrderOfPointsForShortestClosePath(points) | |||||
{ | |||||
let order = []; | |||||
let distances = []; | |||||
if (points.length <= 3) | |||||
{ | |||||
for (let i = 0; i < points.length; ++i) | |||||
order.push(i); | |||||
return order; | |||||
} | |||||
// Just add the first 3 points | |||||
let pointsToAdd = deepcopy(points); | |||||
for (let i = 0; i < 3; ++i) | |||||
{ | |||||
order.push(i); | |||||
pointsToAdd.shift(i); | |||||
if (i) | |||||
distances.push(getDistance(points[order[i]].x, points[order[i]].y, points[order[i - 1]].x, points[order[i - 1]].y)); | |||||
} | |||||
distances.push(getDistance( | |||||
points[order[0]].x, | |||||
points[order[0]].y, | |||||
points[order[order.length - 1]].x, | |||||
points[order[order.length - 1]].y)); | |||||
// Add remaining points so the path lengthens the least | |||||
let numPointsToAdd = pointsToAdd.length; | |||||
for (let i = 0; i < numPointsToAdd; ++i) | |||||
{ | |||||
let indexToAddTo = undefined; | |||||
let minEnlengthen = Infinity; | |||||
let minDist1 = 0; | |||||
let minDist2 = 0; | |||||
for (let k = 0; k < order.length; ++k) | |||||
{ | |||||
let dist1 = getDistance(pointsToAdd[0].x, pointsToAdd[0].y, points[order[k]].x, points[order[k]].y); | |||||
let dist2 = getDistance(pointsToAdd[0].x, pointsToAdd[0].y, points[order[(k + 1) % order.length]].x, points[order[(k + 1) % order.length]].y); | |||||
let enlengthen = dist1 + dist2 - distances[k]; | |||||
if (enlengthen < minEnlengthen) | |||||
{ | |||||
indexToAddTo = k; | |||||
minEnlengthen = enlengthen; | |||||
minDist1 = dist1; | |||||
minDist2 = dist2; | |||||
} | |||||
} | |||||
order.splice(indexToAddTo + 1, 0, i + 3); | |||||
distances.splice(indexToAddTo, 1, minDist1, minDist2); | |||||
pointsToAdd.shift(); | |||||
} | |||||
return order; | |||||
} | |||||
/** | |||||
* Drags a path to a target height smoothing it at the edges and return some points along the path. | * Drags a path to a target height smoothing it at the edges and return some points along the path. | ||||
*/ | */ | ||||
function placeRandomPathToHeight( | function placeRandomPathToHeight( | ||||
start, target, targetHeight, tileClass = undefined, texture = "road_rome_a", | start, target, targetHeight, tileClass = undefined, texture = "road_rome_a", | ||||
width = 10, distance = 4, strength = 0.08, heightmap = g_Map.height) | width = 10, distance = 4, strength = 0.08, heightmap = g_Map.height) | ||||
{ | { | ||||
let pathPoints = []; | let pathPoints = []; | ||||
let position = deepcopy(start); | let position = deepcopy(start); | ||||
Show All 16 Lines | while (true) | ||||
let angleToTarget = getAngle(position.x, position.y, target.x, target.y); | let angleToTarget = getAngle(position.x, position.y, target.x, target.y); | ||||
let angleOff = randFloat(-PI/2, PI/2); | let angleOff = randFloat(-PI/2, PI/2); | ||||
position.x += distance * cos(angleToTarget + angleOff); | position.x += distance * cos(angleToTarget + angleOff); | ||||
position.y += distance * sin(angleToTarget + angleOff); | position.y += distance * sin(angleToTarget + angleOff); | ||||
} | } | ||||
return pathPoints; | return pathPoints; | ||||
} | } | ||||
function getGrad(wrapped = true, scalarField = g_Map.height) | |||||
{ | |||||
let vectorField = []; | |||||
let max_x = scalarField.length; | |||||
let max_y = scalarField[0].length; | |||||
if (!wrapped) | |||||
{ | |||||
max_x -= 1; | |||||
max_y -= 1; | |||||
} | |||||
for (let x = 0; x < max_x; ++x) | |||||
{ | |||||
vectorField.push([]); | |||||
for (let y = 0; y < max_y; ++y) | |||||
vectorField[x].push({"x": scalarField[(x + 1) % max_x][y] - scalarField[x][y], "y": scalarField[x][(y + 1) % max_y] - scalarField[x][y]}); | |||||
} | |||||
return vectorField; | |||||
} | |||||
function splashErodeMap(strength = 1, heightmap = g_Map.height) | |||||
{ | |||||
let max_x = heightmap.length; | |||||
let max_y = heightmap[0].length; | |||||
let dHeight = getGrad(heightmap); | |||||
for (let x = 0; x < max_x; ++x) | |||||
{ | |||||
let next_x = (x + 1) % max_x; | |||||
let prev_x = (x + max_x - 1) % max_x; | |||||
for (let y = 0; y < max_y; ++y) | |||||
{ | |||||
let next_y = (y + 1) % max_y; | |||||
let prev_y = (y + max_y - 1) % max_y; | |||||
let slopes = [- dHeight[x][y].x, - dHeight[x][y].y, dHeight[prev_x][y].x, dHeight[x][prev_y].y]; | |||||
let sumSlopes = 0; | |||||
for (let i = 0; i < slopes.length; ++i) | |||||
if (slopes[i] > 0) | |||||
sumSlopes += slopes[i]; | |||||
let drain = []; | |||||
for (let i = 0; i < slopes.length; ++i) | |||||
{ | |||||
drain.push(0); | |||||
if (slopes[i] > 0) | |||||
drain[i] += min(strength * slopes[i] / sumSlopes, slopes[i]); | |||||
} | |||||
let sumDrain = 0; | |||||
for (let i = 0; i < drain.length; ++i) | |||||
sumDrain += drain[i]; | |||||
// Apply changes to maps | |||||
heightmap[x][y] -= sumDrain; | |||||
heightmap[next_x][y] += drain[0]; | |||||
heightmap[x][next_y] += drain[1]; | |||||
heightmap[prev_x][y] += drain[2]; | |||||
heightmap[x][prev_y] += drain[3]; | |||||
} | |||||
} | |||||
} | |||||
/** | |||||
* Meant to place e.g. resource spots within a height range | |||||
* @param {array} [heightRange] - The height range in which to place the entities (An associative array with keys "min" and "max" each containing a float) | |||||
* @param {array} [avoidPoints=[]] - An array of objects of the form {"x": int, "y": int, "dist": int}, points that will be avoided in the given dist e.g. start locations | |||||
* @param {object} [avoidClass=undefined] - TileClass to be avoided | |||||
* @param {integer} [minDistance=30] - How many tile widths the entities to place have to be away from each other, start locations and the map border | |||||
* @param {array} [heightmap=g_Map.height] - The reliefmap the entities should be distributed on | |||||
* @param {integer} [maxTries=2 * g_Map.size] - How often random player distributions are rolled to be compared (256 to 1024) | |||||
* @param {boolean} [isCircular=g_MapSettings.CircularMap] - If the map is circular or rectangular | |||||
*/ | |||||
function getPointsByHeight(heightRange, avoidPoints = [], avoidClass = undefined, minDistance = 20, maxTries = 2 * g_Map.size, heightmap = g_Map.height, isCircular = g_MapSettings.CircularMap) | |||||
{ | |||||
let points = []; | |||||
let placements = deepcopy(avoidPoints); | |||||
let validVertices = []; | |||||
let r = 0.5 * (heightmap.length - 1); // Map center x/y as well as radius | |||||
let avoidMap; | |||||
if (avoidClass !== undefined) | |||||
avoidMap = g_Map.tileClasses[avoidClass].inclusionCount; | |||||
for (let x = minDistance; x < heightmap.length - minDistance; ++x) | |||||
{ | |||||
for (let y = minDistance; y < heightmap[0].length - minDistance; ++y) | |||||
{ | |||||
if (avoidClass !== undefined && // Avoid adjecting tiles in avoidClass | |||||
(avoidMap[max(x - 1, 0)][y] > 0 || | |||||
avoidMap[x][max(y - 1, 0)] > 0 || | |||||
avoidMap[min(x + 1, avoidMap.length - 1)][y] > 0 || | |||||
avoidMap[x][min(y + 1, avoidMap[0].length - 1)] > 0)) | |||||
continue; | |||||
if (heightmap[x][y] > heightRange.min && heightmap[x][y] < heightRange.max && // Has correct height | |||||
(!isCircular || r - getDistance(x, y, r, r) >= minDistance)) // Enough distance to map border | |||||
validVertices.push({ "x": x, "y": y , "dist": minDistance}); | |||||
} | |||||
} | |||||
for (let tries = 0; tries < maxTries; ++tries) | |||||
{ | |||||
let point = pickRandom(validVertices); | |||||
if (placements.every(p => getDistance(p.x, p.y, point.x, point.y) > max(minDistance, p.dist))) | |||||
{ | |||||
points.push(point); | |||||
placements.push(point); | |||||
} | |||||
if (tries != 0 && tries % 100 == 0) // Time Check | |||||
log(points.length + " points found after " + tries + " tries after " + ((Date.now() - genStartTime) / 1000) + "s"); | |||||
} | |||||
return points; | |||||
} | |||||
/** | /** | ||||
* Design resource spots | * Design resource spots | ||||
*/ | */ | ||||
// Mines | // Mines | ||||
let decorations = [ | let decorations = [ | ||||
"actor|geology/gray1.xml", "actor|geology/gray_rock1.xml", | "actor|geology/gray1.xml", "actor|geology/gray_rock1.xml", | ||||
"actor|geology/highland1.xml", "actor|geology/highland2.xml", "actor|geology/highland3.xml", | "actor|geology/highland1.xml", "actor|geology/highland2.xml", "actor|geology/highland3.xml", | ||||
"actor|geology/highland_c.xml", "actor|geology/highland_d.xml", "actor|geology/highland_e.xml", | "actor|geology/highland_c.xml", "actor|geology/highland_d.xml", "actor|geology/highland_e.xml", | ||||
▲ Show 20 Lines • Show All 477 Lines • Show Last 20 Lines |
Wildfire Games · Phabricator