| [18449] | 1 | /**
|
|---|
| [20898] | 2 | * @file The RandomMap stores the elevation grid, terrain textures and entities that are exported to the engine.
|
|---|
| [18840] | 3 | *
|
|---|
| [23992] | 4 | * @param {number} baseHeight - Initial elevation of the map
|
|---|
| [20932] | 5 | * @param {String|Array} baseTerrain - One or more texture names
|
|---|
| [18449] | 6 | */
|
|---|
| [20932] | 7 | function RandomMap(baseHeight, baseTerrain)
|
|---|
| [9096] | 8 | {
|
|---|
| [21073] | 9 | this.logger = new RandomMapLogger();
|
|---|
| [20932] | 10 |
|
|---|
| [9271] | 11 | // Size must be 0 to 1024, divisible by patches
|
|---|
| [20932] | 12 | this.size = g_MapSettings.Size;
|
|---|
| [18840] | 13 |
|
|---|
| [20932] | 14 | // Create name <-> id maps for textures
|
|---|
| 15 | this.nameToID = {};
|
|---|
| 16 | this.IDToName = [];
|
|---|
| 17 |
|
|---|
| 18 | // Texture 2D array
|
|---|
| [18449] | 19 | this.texture = [];
|
|---|
| [20932] | 20 | for (let x = 0; x < this.size; ++x)
|
|---|
| 21 | {
|
|---|
| 22 | this.texture[x] = new Uint16Array(this.size);
|
|---|
| 23 |
|
|---|
| 24 | for (let z = 0; z < this.size; ++z)
|
|---|
| 25 | this.texture[x][z] = this.getTextureID(
|
|---|
| 26 | typeof baseTerrain == "string" ? baseTerrain : pickRandom(baseTerrain));
|
|---|
| 27 | }
|
|---|
| 28 |
|
|---|
| 29 | // Create 2D arrays for terrain objects and areas
|
|---|
| [21069] | 30 | this.terrainEntities = [];
|
|---|
| [18840] | 31 |
|
|---|
| [20932] | 32 | for (let i = 0; i < this.size; ++i)
|
|---|
| [9096] | 33 | {
|
|---|
| [21069] | 34 | this.terrainEntities[i] = [];
|
|---|
| [20932] | 35 | for (let j = 0; j < this.size; ++j)
|
|---|
| [21069] | 36 | this.terrainEntities[i][j] = undefined;
|
|---|
| [9096] | 37 | }
|
|---|
| [18840] | 38 |
|
|---|
| [9096] | 39 | // Create 2D array for heightmap
|
|---|
| [20932] | 40 | let mapSize = this.size;
|
|---|
| [18449] | 41 | if (!TILE_CENTERED_HEIGHT_MAP)
|
|---|
| 42 | ++mapSize;
|
|---|
| [18840] | 43 |
|
|---|
| [18449] | 44 | this.height = [];
|
|---|
| [17890] | 45 | for (let i = 0; i < mapSize; ++i)
|
|---|
| [9096] | 46 | {
|
|---|
| [18449] | 47 | this.height[i] = new Float32Array(mapSize);
|
|---|
| [18840] | 48 |
|
|---|
| [17890] | 49 | for (let j = 0; j < mapSize; ++j)
|
|---|
| [9096] | 50 | this.height[i][j] = baseHeight;
|
|---|
| 51 | }
|
|---|
| [18840] | 52 |
|
|---|
| [21069] | 53 | this.entities = [];
|
|---|
| [20371] | 54 |
|
|---|
| [18449] | 55 | // Starting entity ID, arbitrary number to leave some space for player entities
|
|---|
| [9096] | 56 | this.entityCount = 150;
|
|---|
| 57 | }
|
|---|
| 58 |
|
|---|
| [21133] | 59 | /**
|
|---|
| 60 | * Prints a timed log entry to stdout and the logfile.
|
|---|
| 61 | */
|
|---|
| [21073] | 62 | RandomMap.prototype.log = function(text)
|
|---|
| 63 | {
|
|---|
| 64 | this.logger.print(text);
|
|---|
| 65 | };
|
|---|
| 66 |
|
|---|
| [20371] | 67 | /**
|
|---|
| [21133] | 68 | * Loads an imagefile and uses it as the heightmap for the current map.
|
|---|
| 69 | * Scales the map (including height) proportionally with the mapsize.
|
|---|
| 70 | */
|
|---|
| 71 | RandomMap.prototype.LoadMapTerrain = function(filename)
|
|---|
| 72 | {
|
|---|
| 73 | g_Map.log("Loading terrain file " + filename);
|
|---|
| [28036] | 74 | const mapTerrain = Engine.LoadMapTerrain("maps/random/" + filename + ".pmp");
|
|---|
| [21133] | 75 |
|
|---|
| [28036] | 76 | const heightmapPainter = new HeightmapPainter(convertHeightmap1Dto2D(mapTerrain.height));
|
|---|
| [21133] | 77 |
|
|---|
| 78 | createArea(
|
|---|
| 79 | new MapBoundsPlacer(),
|
|---|
| 80 | [
|
|---|
| 81 | heightmapPainter,
|
|---|
| 82 | new TerrainTextureArrayPainter(mapTerrain.textureIDs, mapTerrain.textureNames)
|
|---|
| 83 | ]);
|
|---|
| 84 |
|
|---|
| 85 | return heightmapPainter.getScale();
|
|---|
| 86 | };
|
|---|
| 87 |
|
|---|
| 88 | /**
|
|---|
| 89 | * Loads PMP terrain file that contains elevation grid and terrain textures created in atlas.
|
|---|
| 90 | * Scales the map (including height) proportionally with the mapsize.
|
|---|
| 91 | * 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.
|
|---|
| 92 | */
|
|---|
| 93 | RandomMap.prototype.LoadHeightmapImage = function(filename, normalMinHeight, normalMaxHeight)
|
|---|
| 94 | {
|
|---|
| 95 | g_Map.log("Loading heightmap " + filename);
|
|---|
| 96 |
|
|---|
| [28036] | 97 | const heightmapPainter = new HeightmapPainter(
|
|---|
| [21227] | 98 | convertHeightmap1Dto2D(Engine.LoadHeightmapImage("maps/random/" + filename)), normalMinHeight, normalMaxHeight);
|
|---|
| [21133] | 99 |
|
|---|
| 100 | createArea(
|
|---|
| 101 | new MapBoundsPlacer(),
|
|---|
| 102 | heightmapPainter);
|
|---|
| 103 |
|
|---|
| 104 | return heightmapPainter.getScale();
|
|---|
| 105 | };
|
|---|
| 106 |
|
|---|
| 107 | /**
|
|---|
| [20371] | 108 | * Returns the ID of a texture name.
|
|---|
| 109 | * Creates a new ID if there isn't one assigned yet.
|
|---|
| 110 | */
|
|---|
| [20898] | 111 | RandomMap.prototype.getTextureID = function(texture)
|
|---|
| [9096] | 112 | {
|
|---|
| [18449] | 113 | if (texture in this.nameToID)
|
|---|
| [9096] | 114 | return this.nameToID[texture];
|
|---|
| [18840] | 115 |
|
|---|
| [28036] | 116 | const id = this.IDToName.length;
|
|---|
| [9096] | 117 | this.nameToID[texture] = id;
|
|---|
| 118 | this.IDToName[id] = texture;
|
|---|
| [18840] | 119 |
|
|---|
| [9096] | 120 | return id;
|
|---|
| 121 | };
|
|---|
| 122 |
|
|---|
| [20371] | 123 | /**
|
|---|
| 124 | * Returns the next unused entityID.
|
|---|
| 125 | */
|
|---|
| [20898] | 126 | RandomMap.prototype.getEntityID = function()
|
|---|
| [9096] | 127 | {
|
|---|
| 128 | return this.entityCount++;
|
|---|
| [18449] | 129 | };
|
|---|
| [9096] | 130 |
|
|---|
| [20998] | 131 | RandomMap.prototype.isCircularMap = function()
|
|---|
| 132 | {
|
|---|
| 133 | return !!g_MapSettings.CircularMap;
|
|---|
| 134 | };
|
|---|
| 135 |
|
|---|
| 136 | RandomMap.prototype.getSize = function()
|
|---|
| 137 | {
|
|---|
| 138 | return this.size;
|
|---|
| 139 | };
|
|---|
| 140 |
|
|---|
| [25894] | 141 | RandomMap.prototype.getArea = function(size = this.size)
|
|---|
| 142 | {
|
|---|
| [28122] | 143 | return this.isCircularMap() ? diskArea(size / 2) : size * size;
|
|---|
| [25894] | 144 | };
|
|---|
| 145 |
|
|---|
| [20371] | 146 | /**
|
|---|
| [20996] | 147 | * Returns the center tile coordinates of the map.
|
|---|
| 148 | */
|
|---|
| 149 | RandomMap.prototype.getCenter = function()
|
|---|
| 150 | {
|
|---|
| 151 | return deepfreeze(new Vector2D(this.size / 2, this.size / 2));
|
|---|
| [20998] | 152 | };
|
|---|
| [20996] | 153 |
|
|---|
| 154 | /**
|
|---|
| 155 | * Returns a human-readable reference to the smallest and greatest coordinates of the map.
|
|---|
| 156 | */
|
|---|
| 157 | RandomMap.prototype.getBounds = function()
|
|---|
| 158 | {
|
|---|
| 159 | return deepfreeze({
|
|---|
| 160 | "left": 0,
|
|---|
| 161 | "right": this.size,
|
|---|
| 162 | "top": this.size,
|
|---|
| 163 | "bottom": 0
|
|---|
| 164 | });
|
|---|
| [20998] | 165 | };
|
|---|
| [20996] | 166 |
|
|---|
| 167 | /**
|
|---|
| [21069] | 168 | * Determines whether the given coordinates are within the given distance of the map area.
|
|---|
| [21104] | 169 | * Should be used to restrict actor placement.
|
|---|
| [21069] | 170 | * Entity placement should be checked against validTilePassable to exclude the map border.
|
|---|
| [21104] | 171 | * Terrain texture changes should be tested against inMapBounds.
|
|---|
| [20371] | 172 | */
|
|---|
| [20993] | 173 | RandomMap.prototype.validTile = function(position, distance = 0)
|
|---|
| [9096] | 174 | {
|
|---|
| [20998] | 175 | if (this.isCircularMap())
|
|---|
| [20996] | 176 | return Math.round(position.distanceTo(this.getCenter())) < this.size / 2 - distance - 1;
|
|---|
| [20993] | 177 |
|
|---|
| 178 | return position.x >= distance && position.y >= distance && position.x < this.size - distance && position.y < this.size - distance;
|
|---|
| [9096] | 179 | };
|
|---|
| 180 |
|
|---|
| [20371] | 181 | /**
|
|---|
| [21069] | 182 | * Determines whether the given coordinates are within the given distance of the passable map area.
|
|---|
| 183 | * Should be used to restrict entity placement and path creation.
|
|---|
| 184 | */
|
|---|
| 185 | RandomMap.prototype.validTilePassable = function(position, distance = 0)
|
|---|
| 186 | {
|
|---|
| 187 | return this.validTile(position, distance + MAP_BORDER_WIDTH);
|
|---|
| 188 | };
|
|---|
| 189 |
|
|---|
| 190 | /**
|
|---|
| [20371] | 191 | * Determines whether the given coordinates are within the tile grid, passable or not.
|
|---|
| 192 | * Should be used to restrict texture painting.
|
|---|
| 193 | */
|
|---|
| [20979] | 194 | RandomMap.prototype.inMapBounds = function(position)
|
|---|
| [13004] | 195 | {
|
|---|
| [20979] | 196 | return position.x >= 0 && position.y >= 0 && position.x < this.size && position.y < this.size;
|
|---|
| [18449] | 197 | };
|
|---|
| [13004] | 198 |
|
|---|
| [20371] | 199 | /**
|
|---|
| 200 | * Determines whether the given coordinates are within the heightmap grid.
|
|---|
| 201 | * Should be used to restrict elevation changes.
|
|---|
| 202 | */
|
|---|
| [20994] | 203 | RandomMap.prototype.validHeight = function(position)
|
|---|
| [9096] | 204 | {
|
|---|
| [20994] | 205 | if (position.x < 0 || position.y < 0)
|
|---|
| [18449] | 206 | return false;
|
|---|
| [20994] | 207 |
|
|---|
| [11158] | 208 | if (TILE_CENTERED_HEIGHT_MAP)
|
|---|
| [20994] | 209 | return position.x < this.size && position.y < this.size;
|
|---|
| 210 |
|
|---|
| 211 | return position.x <= this.size && position.y <= this.size;
|
|---|
| [9096] | 212 | };
|
|---|
| 213 |
|
|---|
| [20371] | 214 | /**
|
|---|
| [21069] | 215 | * Returns a random point on the map.
|
|---|
| 216 | * @param passableOnly - Should be true for entity placement and false for terrain or elevation operations.
|
|---|
| 217 | */
|
|---|
| 218 | RandomMap.prototype.randomCoordinate = function(passableOnly)
|
|---|
| 219 | {
|
|---|
| [28036] | 220 | const border = passableOnly ? MAP_BORDER_WIDTH : 0;
|
|---|
| [21069] | 221 |
|
|---|
| 222 | if (this.isCircularMap())
|
|---|
| 223 | // Polar coordinates
|
|---|
| 224 | // Uniformly distributed on the disk
|
|---|
| 225 | return Vector2D.add(
|
|---|
| 226 | this.getCenter(),
|
|---|
| 227 | new Vector2D((this.size / 2 - border) * Math.sqrt(randFloat(0, 1)), 0).rotate(randomAngle()).floor());
|
|---|
| 228 |
|
|---|
| 229 | // Rectangular coordinates
|
|---|
| 230 | return new Vector2D(
|
|---|
| 231 | randIntExclusive(border, this.size - border),
|
|---|
| 232 | randIntExclusive(border, this.size - border));
|
|---|
| 233 | };
|
|---|
| 234 |
|
|---|
| 235 | /**
|
|---|
| [20371] | 236 | * Returns the name of the texture of the given tile.
|
|---|
| 237 | */
|
|---|
| [20993] | 238 | RandomMap.prototype.getTexture = function(position)
|
|---|
| [9096] | 239 | {
|
|---|
| [21104] | 240 | if (!this.inMapBounds(position))
|
|---|
| [20993] | 241 | throw new Error("getTexture: invalid tile position " + uneval(position));
|
|---|
| [18840] | 242 |
|
|---|
| [20993] | 243 | return this.IDToName[this.texture[position.x][position.y]];
|
|---|
| [9096] | 244 | };
|
|---|
| 245 |
|
|---|
| [20371] | 246 | /**
|
|---|
| 247 | * Paints the given texture on the given tile.
|
|---|
| 248 | */
|
|---|
| [20988] | 249 | RandomMap.prototype.setTexture = function(position, texture)
|
|---|
| [9096] | 250 | {
|
|---|
| [20988] | 251 | if (position.x < 0 ||
|
|---|
| 252 | position.y < 0 ||
|
|---|
| 253 | position.x >= this.texture.length ||
|
|---|
| 254 | position.y >= this.texture[position.x].length)
|
|---|
| 255 | throw new Error("setTexture: invalid tile position " + uneval(position));
|
|---|
| [18840] | 256 |
|
|---|
| [20988] | 257 | this.texture[position.x][position.y] = this.getTextureID(texture);
|
|---|
| [9096] | 258 | };
|
|---|
| 259 |
|
|---|
| [20983] | 260 | RandomMap.prototype.getHeight = function(position)
|
|---|
| [9096] | 261 | {
|
|---|
| [20994] | 262 | if (!this.validHeight(position))
|
|---|
| [20983] | 263 | throw new Error("getHeight: invalid vertex position " + uneval(position));
|
|---|
| [18840] | 264 |
|
|---|
| [20983] | 265 | return this.height[position.x][position.y];
|
|---|
| [9096] | 266 | };
|
|---|
| 267 |
|
|---|
| [20936] | 268 | RandomMap.prototype.setHeight = function(position, height)
|
|---|
| [9096] | 269 | {
|
|---|
| [20994] | 270 | if (!this.validHeight(position))
|
|---|
| [20936] | 271 | throw new Error("setHeight: invalid vertex position " + uneval(position));
|
|---|
| [18840] | 272 |
|
|---|
| [20936] | 273 | this.height[position.x][position.y] = height;
|
|---|
| [9096] | 274 | };
|
|---|
| 275 |
|
|---|
| [20371] | 276 | /**
|
|---|
| [21069] | 277 | * Adds the given Entity to the map at the location it defines, even if at the impassable map border.
|
|---|
| [20371] | 278 | */
|
|---|
| [21069] | 279 | RandomMap.prototype.placeEntityAnywhere = function(templateName, playerID, position, orientation)
|
|---|
| [9096] | 280 | {
|
|---|
| [28036] | 281 | const entity = new Entity(this.getEntityID(), templateName, playerID, position, orientation);
|
|---|
| [21069] | 282 | this.entities.push(entity);
|
|---|
| [21405] | 283 | return entity;
|
|---|
| [21069] | 284 | };
|
|---|
| [18840] | 285 |
|
|---|
| [21069] | 286 | /**
|
|---|
| 287 | * Adds the given Entity to the map at the location it defines, if that area is not at the impassable map border.
|
|---|
| 288 | */
|
|---|
| 289 | RandomMap.prototype.placeEntityPassable = function(templateName, playerID, position, orientation)
|
|---|
| 290 | {
|
|---|
| [21405] | 291 | if (!this.validTilePassable(position))
|
|---|
| 292 | return undefined;
|
|---|
| 293 |
|
|---|
| 294 | return this.placeEntityAnywhere(templateName, playerID, position, orientation);
|
|---|
| [9096] | 295 | };
|
|---|
| 296 |
|
|---|
| [20371] | 297 | /**
|
|---|
| [21069] | 298 | * Returns the Entity that was painted by a Terrain class on the given tile or undefined otherwise.
|
|---|
| [20371] | 299 | */
|
|---|
| [21069] | 300 | RandomMap.prototype.getTerrainEntity = function(position)
|
|---|
| [9096] | 301 | {
|
|---|
| [21069] | 302 | if (!this.validTilePassable(position))
|
|---|
| 303 | throw new Error("getTerrainEntity: invalid tile position " + uneval(position));
|
|---|
| [18840] | 304 |
|
|---|
| [21069] | 305 | return this.terrainEntities[position.x][position.y];
|
|---|
| [9096] | 306 | };
|
|---|
| 307 |
|
|---|
| [20371] | 308 | /**
|
|---|
| [21069] | 309 | * Places the Entity on the given tile and allows to later replace it if the terrain was painted over.
|
|---|
| [20371] | 310 | */
|
|---|
| [21069] | 311 | RandomMap.prototype.setTerrainEntity = function(templateName, playerID, position, orientation)
|
|---|
| [9096] | 312 | {
|
|---|
| [28036] | 313 | const tilePosition = position.clone().floor();
|
|---|
| [21069] | 314 | if (!this.validTilePassable(tilePosition))
|
|---|
| 315 | throw new Error("setTerrainEntity: invalid tile position " + uneval(position));
|
|---|
| 316 |
|
|---|
| 317 | this.terrainEntities[tilePosition.x][tilePosition.y] =
|
|---|
| 318 | new Entity(this.getEntityID(), templateName, playerID, position, orientation);
|
|---|
| [9096] | 319 | };
|
|---|
| 320 |
|
|---|
| [21300] | 321 | RandomMap.prototype.deleteTerrainEntity = function(position)
|
|---|
| 322 | {
|
|---|
| [28036] | 323 | const tilePosition = position.clone().floor();
|
|---|
| [21300] | 324 | if (!this.validTilePassable(tilePosition))
|
|---|
| 325 | throw new Error("setTerrainEntity: invalid tile position " + uneval(position));
|
|---|
| 326 |
|
|---|
| 327 | this.terrainEntities[tilePosition.x][tilePosition.y] = undefined;
|
|---|
| 328 | };
|
|---|
| 329 |
|
|---|
| [20898] | 330 | RandomMap.prototype.createTileClass = function()
|
|---|
| [9096] | 331 | {
|
|---|
| [21025] | 332 | return new TileClass(this.size);
|
|---|
| [9096] | 333 | };
|
|---|
| 334 |
|
|---|
| [20371] | 335 | /**
|
|---|
| 336 | * Retrieve interpolated height for arbitrary coordinates within the heightmap grid.
|
|---|
| 337 | */
|
|---|
| [20994] | 338 | RandomMap.prototype.getExactHeight = function(position)
|
|---|
| [9096] | 339 | {
|
|---|
| [28036] | 340 | const xi = Math.min(Math.floor(position.x), this.size);
|
|---|
| 341 | const zi = Math.min(Math.floor(position.y), this.size);
|
|---|
| 342 | const xf = position.x - xi;
|
|---|
| 343 | const zf = position.y - zi;
|
|---|
| [18840] | 344 |
|
|---|
| [28036] | 345 | const h00 = this.height[xi][zi];
|
|---|
| 346 | const h01 = this.height[xi][zi + 1];
|
|---|
| 347 | const h10 = this.height[xi + 1][zi];
|
|---|
| 348 | const h11 = this.height[xi + 1][zi + 1];
|
|---|
| [18840] | 349 |
|
|---|
| [18449] | 350 | return (1 - zf) * ((1 - xf) * h00 + xf * h10) + zf * ((1 - xf) * h01 + xf * h11);
|
|---|
| [9096] | 351 | };
|
|---|
| 352 |
|
|---|
| [11158] | 353 | // Converts from the tile centered height map to the corner based height map, used when TILE_CENTERED_HEIGHT_MAP = true
|
|---|
| [20994] | 354 | RandomMap.prototype.cornerHeight = function(position)
|
|---|
| [11158] | 355 | {
|
|---|
| [18449] | 356 | let count = 0;
|
|---|
| 357 | let sumHeight = 0;
|
|---|
| [18840] | 358 |
|
|---|
| [28036] | 359 | for (const vertex of g_TileVertices)
|
|---|
| [20994] | 360 | {
|
|---|
| [28036] | 361 | const pos = Vector2D.sub(position, vertex);
|
|---|
| [20994] | 362 | if (this.validHeight(pos))
|
|---|
| [11158] | 363 | {
|
|---|
| [18449] | 364 | ++count;
|
|---|
| [20994] | 365 | sumHeight += this.getHeight(pos);
|
|---|
| [11158] | 366 | }
|
|---|
| [20994] | 367 | }
|
|---|
| [18840] | 368 |
|
|---|
| [20998] | 369 | if (!count)
|
|---|
| [11158] | 370 | return 0;
|
|---|
| [18840] | 371 |
|
|---|
| [11158] | 372 | return sumHeight / count;
|
|---|
| 373 | };
|
|---|
| 374 |
|
|---|
| [20943] | 375 | RandomMap.prototype.getAdjacentPoints = function(position)
|
|---|
| 376 | {
|
|---|
| [28036] | 377 | const adjacentPositions = [];
|
|---|
| [20943] | 378 |
|
|---|
| [28036] | 379 | for (const adjacentCoordinate of g_AdjacentCoordinates)
|
|---|
| [21175] | 380 | {
|
|---|
| [28036] | 381 | const adjacentPos = Vector2D.add(position, adjacentCoordinate).round();
|
|---|
| [21175] | 382 | if (this.inMapBounds(adjacentPos))
|
|---|
| 383 | adjacentPositions.push(adjacentPos);
|
|---|
| 384 | }
|
|---|
| [20943] | 385 |
|
|---|
| 386 | return adjacentPositions;
|
|---|
| [20998] | 387 | };
|
|---|
| [20943] | 388 |
|
|---|
| [20370] | 389 | /**
|
|---|
| [20943] | 390 | * Returns the average height of adjacent tiles, helpful for smoothing.
|
|---|
| 391 | */
|
|---|
| 392 | RandomMap.prototype.getAverageHeight = function(position)
|
|---|
| 393 | {
|
|---|
| [28036] | 394 | const adjacentPositions = this.getAdjacentPoints(position);
|
|---|
| [20943] | 395 | if (!adjacentPositions.length)
|
|---|
| 396 | return 0;
|
|---|
| 397 |
|
|---|
| [20983] | 398 | return adjacentPositions.reduce((totalHeight, pos) => totalHeight + this.getHeight(pos), 0) / adjacentPositions.length;
|
|---|
| [20998] | 399 | };
|
|---|
| [20943] | 400 |
|
|---|
| 401 | /**
|
|---|
| 402 | * Returns the steepness of the given location, defined as the average height difference of the adjacent tiles.
|
|---|
| 403 | */
|
|---|
| 404 | RandomMap.prototype.getSlope = function(position)
|
|---|
| 405 | {
|
|---|
| [28036] | 406 | const adjacentPositions = this.getAdjacentPoints(position);
|
|---|
| [20943] | 407 | if (!adjacentPositions.length)
|
|---|
| 408 | return 0;
|
|---|
| 409 |
|
|---|
| [20983] | 410 | return adjacentPositions.reduce((totalSlope, adjacentPos) =>
|
|---|
| 411 | totalSlope + Math.abs(this.getHeight(adjacentPos) - this.getHeight(position)), 0) / adjacentPositions.length;
|
|---|
| [20998] | 412 | };
|
|---|
| [20943] | 413 |
|
|---|
| 414 | /**
|
|---|
| [20370] | 415 | * Retrieve an array of all Entities placed on the map.
|
|---|
| 416 | */
|
|---|
| [20898] | 417 | RandomMap.prototype.exportEntityList = function()
|
|---|
| [9096] | 418 | {
|
|---|
| [28036] | 419 | const nonTerrainCount = this.entities.length;
|
|---|
| [21297] | 420 |
|
|---|
| [18449] | 421 | // Change rotation from simple 2d to 3d befor giving to engine
|
|---|
| [28036] | 422 | for (const entity of this.entities)
|
|---|
| [21069] | 423 | entity.rotation.y = Math.PI / 2 - entity.rotation.y;
|
|---|
| [18840] | 424 |
|
|---|
| [18449] | 425 | // Terrain objects e.g. trees
|
|---|
| [20370] | 426 | for (let x = 0; x < this.size; ++x)
|
|---|
| 427 | for (let z = 0; z < this.size; ++z)
|
|---|
| [21069] | 428 | if (this.terrainEntities[x][z])
|
|---|
| 429 | this.entities.push(this.terrainEntities[x][z]);
|
|---|
| [18840] | 430 |
|
|---|
| [21297] | 431 | this.logger.printDirectly(
|
|---|
| 432 | "Total entities: " + this.entities.length + ", " +
|
|---|
| 433 | "Terrain entities: " + (this.entities.length - nonTerrainCount) + ", " +
|
|---|
| 434 | "Textures: " + this.IDToName.length + ".\n");
|
|---|
| 435 |
|
|---|
| [21069] | 436 | return this.entities;
|
|---|
| [18437] | 437 | };
|
|---|
| 438 |
|
|---|
| [20370] | 439 | /**
|
|---|
| 440 | * Convert the elevation grid to a one-dimensional array.
|
|---|
| 441 | */
|
|---|
| [20898] | 442 | RandomMap.prototype.exportHeightData = function()
|
|---|
| [18437] | 443 | {
|
|---|
| [28036] | 444 | const heightmapSize = this.size + 1;
|
|---|
| 445 | const heightmap = new Uint16Array(Math.square(heightmapSize));
|
|---|
| [18840] | 446 |
|
|---|
| [20370] | 447 | for (let x = 0; x < heightmapSize; ++x)
|
|---|
| 448 | for (let z = 0; z < heightmapSize; ++z)
|
|---|
| [9096] | 449 | {
|
|---|
| [28036] | 450 | const position = new Vector2D(x, z);
|
|---|
| 451 | const currentHeight = TILE_CENTERED_HEIGHT_MAP ? this.cornerHeight(position) : this.getHeight(position);
|
|---|
| [18840] | 452 |
|
|---|
| [18449] | 453 | // Correct height by SEA_LEVEL and prevent under/overflow in terrain data
|
|---|
| [20370] | 454 | heightmap[z * heightmapSize + x] = Math.max(0, Math.min(0xFFFF, Math.floor((currentHeight + SEA_LEVEL) * HEIGHT_UNITS_PER_METRE)));
|
|---|
| [9096] | 455 | }
|
|---|
| [18840] | 456 |
|
|---|
| [20370] | 457 | return heightmap;
|
|---|
| 458 | };
|
|---|
| [18840] | 459 |
|
|---|
| [20370] | 460 | /**
|
|---|
| 461 | * Assemble terrain textures in a one-dimensional array.
|
|---|
| 462 | */
|
|---|
| [20898] | 463 | RandomMap.prototype.exportTerrainTextures = function()
|
|---|
| [20370] | 464 | {
|
|---|
| [28036] | 465 | const tileIndex = new Uint16Array(Math.square(this.size));
|
|---|
| 466 | const tilePriority = new Uint16Array(Math.square(this.size));
|
|---|
| [18840] | 467 |
|
|---|
| [18449] | 468 | for (let x = 0; x < this.size; ++x)
|
|---|
| 469 | for (let z = 0; z < this.size; ++z)
|
|---|
| [9096] | 470 | {
|
|---|
| [9271] | 471 | // TODO: For now just use the texture's index as priority, might want to do this another way
|
|---|
| [18449] | 472 | tileIndex[z * this.size + x] = this.texture[x][z];
|
|---|
| 473 | tilePriority[z * this.size + x] = this.texture[x][z];
|
|---|
| [9096] | 474 | }
|
|---|
| [18840] | 475 |
|
|---|
| [20370] | 476 | return {
|
|---|
| 477 | "index": tileIndex,
|
|---|
| 478 | "priority": tilePriority
|
|---|
| 479 | };
|
|---|
| [9096] | 480 | };
|
|---|
| [20932] | 481 |
|
|---|
| [28093] | 482 | RandomMap.prototype.MakeExportable = function()
|
|---|
| [20932] | 483 | {
|
|---|
| 484 | if (g_Environment.Water.WaterBody.Height === undefined)
|
|---|
| 485 | g_Environment.Water.WaterBody.Height = SEA_LEVEL - 0.1;
|
|---|
| 486 |
|
|---|
| [21073] | 487 | this.logger.close();
|
|---|
| 488 |
|
|---|
| [28093] | 489 | return {
|
|---|
| [20932] | 490 | "entities": this.exportEntityList(),
|
|---|
| 491 | "height": this.exportHeightData(),
|
|---|
| 492 | "seaLevel": SEA_LEVEL,
|
|---|
| 493 | "size": this.size,
|
|---|
| 494 | "textureNames": this.IDToName,
|
|---|
| 495 | "tileData": this.exportTerrainTextures(),
|
|---|
| 496 | "Camera": g_Camera,
|
|---|
| 497 | "Environment": g_Environment
|
|---|
| [28093] | 498 | };
|
|---|
| [20998] | 499 | };
|
|---|
| [28093] | 500 |
|
|---|
| 501 | RandomMap.prototype.ExportMap = function()
|
|---|
| 502 | {
|
|---|
| 503 | Engine.ExportMap(this.MakeExportable());
|
|---|
| 504 | };
|
|---|