forked from 0ad/0ad
407 lines
8.8 KiB
JavaScript
407 lines
8.8 KiB
JavaScript
//////////////////////////////////////////////////////////////////////
|
|
// Map
|
|
//
|
|
// Class for holding map data and providing basic API to change it
|
|
//
|
|
// size: Size of the map in tiles
|
|
// 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 = new Array(size);
|
|
this.terrainObjects = new Array(size);
|
|
this.area = new Array(size);
|
|
|
|
for (var i = 0; i < size; i++)
|
|
{
|
|
this.texture[i] = new Uint16Array(size); // uint16 - texture IDs
|
|
this.terrainObjects[i] = new Array(size); // array of entities
|
|
this.area[i] = new Uint16Array(size); // uint16 - area IDs
|
|
|
|
for (var j = 0; j < size; j++)
|
|
{
|
|
this.terrainObjects[i][j] = [];
|
|
}
|
|
}
|
|
|
|
// Create 2D array for heightmap
|
|
var mapSize;
|
|
if (TILE_CENTERED_HEIGHT_MAP)
|
|
{
|
|
mapSize = size;
|
|
}
|
|
else
|
|
{
|
|
mapSize = size+1;
|
|
}
|
|
|
|
this.height = new Array(mapSize);
|
|
for (var i = 0; i < mapSize; i++)
|
|
{
|
|
this.height[i] = new Float32Array(mapSize); // float32
|
|
|
|
for (var j = 0; j < mapSize; j++)
|
|
{ // Initialize height map to baseHeight
|
|
this.height[i][j] = baseHeight;
|
|
}
|
|
}
|
|
|
|
// Create name <-> id maps for textures
|
|
this.nameToID = {};
|
|
this.IDToName = []; //string
|
|
|
|
// Other arrays
|
|
this.objects = []; //object
|
|
this.tileClasses = []; //int
|
|
|
|
this.areaID = 0;
|
|
|
|
// Starting entity ID
|
|
this.entityCount = 150;
|
|
}
|
|
|
|
Map.prototype.initTerrain = function(baseTerrain)
|
|
{
|
|
// Initialize base terrain
|
|
var size = this.size;
|
|
for (var i = 0; i < size; i++)
|
|
{
|
|
for (var j = 0; j < 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
|
|
var 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)
|
|
{
|
|
if (g_MapSettings.CircularMap)
|
|
{ // Within map circle
|
|
var halfSize = Math.floor(0.5*this.size);
|
|
var dx = (x - halfSize);
|
|
var dz = (z - halfSize);
|
|
if (distance === undefined)
|
|
return Math.round(Math.sqrt(dx*dx + dz*dz)) < halfSize - 1;
|
|
else
|
|
return Math.round(Math.sqrt(dx*dx + dz*dz)) < halfSize - distance - 1;
|
|
}
|
|
else
|
|
{ // Within map square
|
|
return x >= 0 && z >= 0 && x < this.size && z < this.size;
|
|
}
|
|
};
|
|
|
|
// 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==false then this is (size + 1 by size + 1) otherwise (size, size)
|
|
Map.prototype.validH = function(x, z)
|
|
{
|
|
if (TILE_CENTERED_HEIGHT_MAP)
|
|
{
|
|
return x >= 0 && z >= 0 && x < this.size && z < this.size;
|
|
}
|
|
else
|
|
{
|
|
return x >= 0 && z >= 0 && 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)
|
|
{
|
|
var painterArray = painter;
|
|
painter = new MultiPainter(painterArray);
|
|
}
|
|
|
|
// Check for null constraint
|
|
if (constraint === undefined || constraint === null)
|
|
{
|
|
constraint = new NullConstraint();
|
|
}
|
|
else if (constraint instanceof Array)
|
|
{ // Check for multiple constraints
|
|
var constraintArray = constraint;
|
|
constraint = new AndConstraint(constraintArray);
|
|
}
|
|
|
|
var points = placer.place(constraint);
|
|
if (!points)
|
|
return undefined;
|
|
|
|
var newID = ++this.areaID;
|
|
var area = new Area(points, newID);
|
|
for (var i=0; i < points.length; i++)
|
|
{
|
|
this.area[points[i].x][points[i].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)
|
|
{ // Check for multiple constraints
|
|
var constraintArray = constraint;
|
|
constraint = new AndConstraint(constraintArray);
|
|
}
|
|
|
|
return placer.place(player, constraint);
|
|
};
|
|
|
|
Map.prototype.createTileClass = function()
|
|
{
|
|
var 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)
|
|
{
|
|
var xi = min(Math.floor(x), this.size);
|
|
var zi = min(Math.floor(z), this.size);
|
|
var xf = x - xi;
|
|
var zf = z - zi;
|
|
|
|
var h00 = this.height[xi][zi];
|
|
var h01 = this.height[xi][zi+1];
|
|
var h10 = this.height[xi+1][zi];
|
|
var 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)
|
|
{
|
|
var count = 0;
|
|
var sumHeight = 0;
|
|
|
|
var dirs = [[-1,-1], [-1,0], [0,-1], [0,0]];
|
|
for each (var dir in dirs)
|
|
{
|
|
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.getMapData = function()
|
|
{
|
|
var data = {};
|
|
|
|
// Build entity array
|
|
var entities = [];
|
|
|
|
// Terrain objects first (trees)
|
|
var size = this.size;
|
|
for (var x = 0; x < size; ++x)
|
|
{
|
|
for (var z = 0; z < size; ++z)
|
|
{
|
|
if (this.terrainObjects[x][z] !== undefined)
|
|
{
|
|
entities.push(this.terrainObjects[x][z]);
|
|
}
|
|
}
|
|
}
|
|
// Now other entities
|
|
for (var i = 0; i < this.objects.length; ++i)
|
|
{
|
|
// Change rotation from simple 2d to 3d befor giving to engine
|
|
this.objects[i].rotation.y = PI/2 - this.objects[i].rotation.y;
|
|
entities.push(this.objects[i]);
|
|
}
|
|
data["entities"] = entities;
|
|
|
|
log("Number of entities: "+entities.length);
|
|
|
|
// Terrain
|
|
data["size"] = this.size;
|
|
|
|
// Convert 2D heightmap array to flat array
|
|
// Flat because it's easier to handle by the engine
|
|
var mapSize = size+1;
|
|
var height16 = new Uint16Array(mapSize*mapSize); // uint16
|
|
for (var x = 0; x < mapSize; x++)
|
|
{
|
|
for (var z = 0; z < mapSize; z++)
|
|
{
|
|
var intHeight;
|
|
if (TILE_CENTERED_HEIGHT_MAP)
|
|
{
|
|
intHeight = Math.floor((this.cornerHeight(x, z) + SEA_LEVEL) * HEIGHT_UNITS_PER_METRE);
|
|
}
|
|
else
|
|
{
|
|
intHeight = Math.floor((this.height[x][z] + SEA_LEVEL) * HEIGHT_UNITS_PER_METRE);
|
|
}
|
|
|
|
// Prevent under/overflow in terrain data
|
|
if (intHeight > 0xFFFF)
|
|
{
|
|
intHeight = 0xFFFF;
|
|
}
|
|
else if (intHeight < 0)
|
|
{
|
|
intHeight = 0;
|
|
}
|
|
|
|
height16[z*mapSize + x] = intHeight;
|
|
}
|
|
}
|
|
data["height"] = height16;
|
|
data["seaLevel"] = SEA_LEVEL;
|
|
|
|
// Get array of textures used in this map
|
|
var textureNames = [];
|
|
for (var name in this.nameToID)
|
|
{
|
|
textureNames.push(name);
|
|
}
|
|
data["textureNames"] = textureNames;
|
|
|
|
// Convert 2D tile data to flat array
|
|
var tileIndex = new Uint16Array(size*size); // uint16
|
|
var tilePriority = new Uint16Array(size*size); // uint16
|
|
for (var x = 0; x < size; x++)
|
|
{
|
|
for (var z = 0; z < size; z++)
|
|
{
|
|
// TODO: For now just use the texture's index as priority, might want to do this another way
|
|
tileIndex[z*size + x] = this.texture[x][z];
|
|
tilePriority[z*size + x] = this.texture[x][z];
|
|
}
|
|
}
|
|
data["tileData"] = {"index": tileIndex, "priority": tilePriority};
|
|
|
|
return data;
|
|
};
|