0ad/binaries/data/mods/public/maps/random/rmgen/map.js
elexis a4a99bc129 Fix a lot of whitespace in gui/.
This was SVN commit r18650.
2016-08-27 15:33:22 +00:00

326 lines
7.9 KiB
JavaScript

/**
* Class for holding map data and providing basic API to change it
*
* @param {int} [size] - Size of the map in tiles
* @param {float} [baseHeight] - Starting height of the map
*/
function Map(size, baseHeight)
{
// Size must be 0 to 1024, divisible by patches
this.size = size;
// Create 2D arrays for textures, object, and areas
this.texture = [];
this.terrainObjects = [];
this.area = [];
for (let i = 0; i < size; ++i)
{
// Texture IDs
this.texture[i] = new Uint16Array(size);
// Entities
this.terrainObjects[i] = [];
// Area IDs
this.area[i] = new Uint16Array(size);
for (let j = 0; j < size; ++j)
this.terrainObjects[i][j] = [];
}
// Create 2D array for heightmap
let mapSize = size;
if (!TILE_CENTERED_HEIGHT_MAP)
++mapSize;
this.height = [];
for (let i = 0; i < mapSize; ++i)
{
this.height[i] = new Float32Array(mapSize);
for (let j = 0; j < mapSize; ++j)
this.height[i][j] = baseHeight;
}
// Create name <-> id maps for textures
this.nameToID = {};
this.IDToName = [];
// Array of objects (entitys/actors)
this.objects = [];
// Array of integers
this.tileClasses = [];
this.areaID = 0;
// Starting entity ID, arbitrary number to leave some space for player entities
this.entityCount = 150;
}
Map.prototype.initTerrain = function(baseTerrain)
{
for (let i = 0; i < this.size; ++i)
for (let j = 0; j < this.size; ++j)
baseTerrain.place(i, j);
};
// Return ID of texture (by name)
Map.prototype.getTextureID = function(texture)
{
if (texture in this.nameToID)
return this.nameToID[texture];
// Add new texture
let id = this.IDToName.length;
this.nameToID[texture] = id;
this.IDToName[id] = texture;
return id;
};
// Return next free entity ID
Map.prototype.getEntityID = function()
{
return this.entityCount++;
};
// Check bounds on tile map
Map.prototype.validT = function(x, z, distance = 0)
{
if (g_MapSettings.CircularMap)
{
let halfSize = Math.floor(this.size / 2);
return Math.round(getDistance(x, z, halfSize, halfSize)) < halfSize - distance - 1;
}
else
return x >= distance && z >= distance && x < this.size - distance && z < this.size - distance;
};
// Check bounds on tile map
Map.prototype.inMapBounds = function(x, z)
{
return x >= 0 && z >= 0 && x < this.size && z < this.size;
};
// Check bounds on height map if TILE_CENTERED_HEIGHT_MAP==true then it's (size, size) otherwise (size + 1 by size + 1)
Map.prototype.validH = function(x, z)
{
if (x < 0 || z < 0)
return false;
if (TILE_CENTERED_HEIGHT_MAP)
return x < this.size && z < this.size;
return x <= this.size && z <= this.size;
};
// Check bounds on tile class
Map.prototype.validClass = function(c)
{
return c >= 0 && c < this.tileClasses.length;
};
Map.prototype.getTexture = function(x, z)
{
if (!this.validT(x, z))
throw "getTexture: invalid tile position (" + x + ", " + z + ")";
return this.IDToName[this.texture[x][z]];
};
Map.prototype.setTexture = function(x, z, texture)
{
if (!this.validT(x, z))
throw "setTexture: invalid tile position (" + x + ", " + z + ")";
this.texture[x][z] = this.getTextureID(texture);
};
Map.prototype.getHeight = function(x, z)
{
if (!this.validH(x, z))
throw "getHeight: invalid vertex position (" + x + ", " + z + ")";
return this.height[x][z];
};
Map.prototype.setHeight = function(x, z, height)
{
if (!this.validH(x, z))
throw "setHeight: invalid vertex position (" + x + ", " + z + ")";
this.height[x][z] = height;
};
Map.prototype.getTerrainObjects = function(x, z)
{
if (!this.validT(x, z))
throw "getTerrainObjects: invalid tile position (" + x + ", " + z + ")";
return this.terrainObjects[x][z];
};
Map.prototype.setTerrainObject = function(x, z, object)
{
if (!this.validT(x, z, 2))
throw "setTerrainObject: invalid tile position (" + x + ", " + z + ")";
this.terrainObjects[x][z] = object;
};
Map.prototype.placeTerrain = function(x, z, terrain)
{
terrain.place(x, z);
};
Map.prototype.addObject = function(obj)
{
this.objects.push(obj);
};
Map.prototype.createArea = function(placer, painter, constraint)
{
// Check for multiple painters
if (painter instanceof Array)
painter = new MultiPainter(painter);
if (constraint === undefined || constraint === null)
constraint = new NullConstraint();
else if (constraint instanceof Array)
// Check for multiple constraints
constraint = new AndConstraint(constraint);
let points = placer.place(constraint);
if (!points)
return undefined;
let newID = ++this.areaID;
let area = new Area(points, newID);
for (let p of points)
this.area[p.x][p.z] = newID;
painter.paint(area);
return area;
};
Map.prototype.createObjectGroup = function(placer, player, constraint)
{
// Check for null constraint
if (constraint === undefined || constraint === null)
constraint = new NullConstraint();
else if (constraint instanceof Array)
constraint = new AndConstraint(constraint);
return placer.place(player, constraint);
};
Map.prototype.createTileClass = function()
{
let newID = this.tileClasses.length;
this.tileClasses.push(new TileClass(this.size, newID));
return newID;
};
// Get height taking into account terrain curvature
Map.prototype.getExactHeight = function(x, z)
{
let xi = Math.min(Math.floor(x), this.size);
let zi = Math.min(Math.floor(z), this.size);
let xf = x - xi;
let zf = z - zi;
let h00 = this.height[xi][zi];
let h01 = this.height[xi][zi + 1];
let h10 = this.height[xi + 1][zi];
let h11 = this.height[xi + 1][zi + 1];
return (1 - zf) * ((1 - xf) * h00 + xf * h10) + zf * ((1 - xf) * h01 + xf * h11);
};
// Converts from the tile centered height map to the corner based height map, used when TILE_CENTERED_HEIGHT_MAP = true
Map.prototype.cornerHeight = function(x, z)
{
let count = 0;
let sumHeight = 0;
for (let dir of [[-1, -1], [-1, 0], [0, -1], [0, 0]])
if (this.validH(x + dir[0], z + dir[1]))
{
++count;
sumHeight += this.height[x + dir[0]][z + dir[1]];
}
if (count == 0)
return 0;
return sumHeight / count;
};
Map.prototype.getFullEntityList = function(rotateForMapExport = false)
{
// Change rotation from simple 2d to 3d befor giving to engine
if (rotateForMapExport)
for (let obj of this.objects)
obj.rotation.y = PI / 2 - obj.rotation.y;
// All non terrain objects
let entities = this.objects;
// Terrain objects e.g. trees
let size = this.size;
for (let x = 0; x < size; ++x)
for (let z = 0; z < size; ++z)
if (this.terrainObjects[x][z] !== undefined)
entities.push(this.terrainObjects[x][z]);
return entities;
};
Map.prototype.getMapData = function()
{
let data = {};
// Convert 2D heightmap array to flat array
// Flat because it's easier to handle by the engine
let mapSize = this.size + 1;
let height = new Uint16Array(mapSize * mapSize);
for (let x = 0; x < mapSize; ++x)
for (let z = 0; z < mapSize; ++z)
{
let currentHeight;
if (TILE_CENTERED_HEIGHT_MAP)
currentHeight = this.cornerHeight(x, z);
else
currentHeight = this.height[x][z];
// Correct height by SEA_LEVEL and prevent under/overflow in terrain data
height[z * mapSize + x] = Math.max(0, Math.min(0xFFFF, Math.floor((currentHeight + SEA_LEVEL) * HEIGHT_UNITS_PER_METRE)));
}
data.height = height;
data.seaLevel = SEA_LEVEL;
// Terrain, map width in tiles
data.size = this.size;
// Get array of textures used in this map
data.textureNames = this.IDToName;
// Entities
data.entities = this.getFullEntityList(true);
log("Number of entities: "+ data.entities.length);
// Convert 2D tile data to flat array
let tileIndex = new Uint16Array(this.size * this.size);
let tilePriority = new Uint16Array(this.size * this.size);
for (let x = 0; x < this.size; ++x)
for (let z = 0; z < this.size; ++z)
{
// TODO: For now just use the texture's index as priority, might want to do this another way
tileIndex[z * this.size + x] = this.texture[x][z];
tilePriority[z * this.size + x] = this.texture[x][z];
}
data.tileData = { "index": tileIndex, "priority": tilePriority };
return data;
};