2 * Class for holding map data and providing basic API to change it
4 * @param {int} [size] - Size of the map in tiles
5 * @param {float} [baseHeight] - Starting height of the map
7 function Map(size, baseHeight)
9 // Size must be 0 to 1024, divisible by patches
12 // Create 2D arrays for textures, object, and areas
14 this.terrainObjects = [];
17 for (let i = 0; i < size; ++i)
20 this.texture[i] = new Uint16Array(size);
22 this.terrainObjects[i] = [];
24 this.area[i] = new Uint16Array(size);
26 for (let j = 0; j < size; ++j)
27 this.terrainObjects[i][j] = [];
30 // Create 2D array for heightmap
32 if (!TILE_CENTERED_HEIGHT_MAP)
36 for (let i = 0; i < mapSize; ++i)
38 this.height[i] = new Float32Array(mapSize);
40 for (let j = 0; j < mapSize; ++j)
41 this.height[i][j] = baseHeight;
44 // Create name <-> id maps for textures
48 // Array of objects (entitys/actors)
51 this.tileClasses = [];
55 // Starting entity ID, arbitrary number to leave some space for player entities
56 this.entityCount = 150;
59 Map.prototype.initTerrain = function(baseTerrain)
61 for (let i = 0; i < this.size; ++i)
62 for (let j = 0; j < this.size; ++j)
63 baseTerrain.place(i, j);
66 // Return ID of texture (by name)
67 Map.prototype.getTextureID = function(texture)
69 if (texture in this.nameToID)
70 return this.nameToID[texture];
73 let id = this.IDToName.length;
74 this.nameToID[texture] = id;
75 this.IDToName[id] = texture;
80 // Return next free entity ID
81 Map.prototype.getEntityID = function()
83 return this.entityCount++;
86 // Check bounds on tile map
87 Map.prototype.validT = function(x, z, distance = 0)
89 distance += MAP_BORDER_WIDTH;
91 if (g_MapSettings.CircularMap)
93 let halfSize = Math.floor(this.size / 2);
94 return Math.round(Math.euclidDistance2D(x, z, halfSize, halfSize)) < halfSize - distance - 1;
97 return x >= distance && z >= distance && x < this.size - distance && z < this.size - distance;
100 // Check bounds on tile map
101 Map.prototype.inMapBounds = function(x, z)
103 return x >= 0 && z >= 0 && x < this.size && z < this.size;
106 // Check bounds on height map if TILE_CENTERED_HEIGHT_MAP==true then it's (size, size) otherwise (size + 1 by size + 1)
107 Map.prototype.validH = function(x, z)
111 if (TILE_CENTERED_HEIGHT_MAP)
112 return x < this.size && z < this.size;
113 return x <= this.size && z <= this.size;
116 // Check bounds on tile class
117 Map.prototype.validClass = function(c)
119 return c >= 0 && c < this.tileClasses.length;
122 Map.prototype.getTexture = function(x, z)
124 if (!this.validT(x, z))
125 throw new Error("getTexture: invalid tile position (" + x + ", " + z + ")");
127 return this.IDToName[this.texture[x][z]];
130 Map.prototype.setTexture = function(x, z, texture)
132 if (!this.validT(x, z))
133 throw new Error("setTexture: invalid tile position (" + x + ", " + z + ")");
135 this.texture[x][z] = this.getTextureID(texture);
138 Map.prototype.getHeight = function(x, z)
140 if (!this.validH(x, z))
141 throw new Error("getHeight: invalid vertex position (" + x + ", " + z + ")");
143 return this.height[x][z];
146 Map.prototype.setHeight = function(x, z, height)
148 if (!this.validH(x, z))
149 throw new Error("setHeight: invalid vertex position (" + x + ", " + z + ")");
151 this.height[x][z] = height;
154 Map.prototype.getTerrainObjects = function(x, z)
156 if (!this.validT(x, z))
157 throw new Error("getTerrainObjects: invalid tile position (" + x + ", " + z + ")");
159 return this.terrainObjects[x][z];
162 Map.prototype.setTerrainObject = function(x, z, object)
164 if (!this.validT(x, z))
165 throw new Error("setTerrainObject: invalid tile position (" + x + ", " + z + ")");
167 this.terrainObjects[x][z] = object;
170 Map.prototype.placeTerrain = function(x, z, terrain)
175 Map.prototype.addObject = function(obj)
177 this.objects.push(obj);
180 Map.prototype.createArea = function(placer, painter, constraint)
182 // Check for multiple painters
183 if (painter instanceof Array)
184 painter = new MultiPainter(painter);
186 if (constraint === undefined || constraint === null)
187 constraint = new NullConstraint();
188 else if (constraint instanceof Array)
189 // Check for multiple constraints
190 constraint = new AndConstraint(constraint);
192 let points = placer.place(constraint);
196 let newID = ++this.areaID;
197 let area = new Area(points, newID);
198 for (let p of points)
199 this.area[p.x][p.z] = newID;
206 Map.prototype.createObjectGroup = function(placer, player, constraint)
208 // Check for null constraint
209 if (constraint === undefined || constraint === null)
210 constraint = new NullConstraint();
211 else if (constraint instanceof Array)
212 constraint = new AndConstraint(constraint);
214 return placer.place(player, constraint);
217 Map.prototype.createTileClass = function()
219 let newID = this.tileClasses.length;
220 this.tileClasses.push(new TileClass(this.size, newID));
225 // Get height taking into account terrain curvature
226 Map.prototype.getExactHeight = function(x, z)
228 let xi = Math.min(Math.floor(x), this.size);
229 let zi = Math.min(Math.floor(z), this.size);
233 let h00 = this.height[xi][zi];
234 let h01 = this.height[xi][zi + 1];
235 let h10 = this.height[xi + 1][zi];
236 let h11 = this.height[xi + 1][zi + 1];
238 return (1 - zf) * ((1 - xf) * h00 + xf * h10) + zf * ((1 - xf) * h01 + xf * h11);
241 // Converts from the tile centered height map to the corner based height map, used when TILE_CENTERED_HEIGHT_MAP = true
242 Map.prototype.cornerHeight = function(x, z)
247 for (let dir of [[-1, -1], [-1, 0], [0, -1], [0, 0]])
248 if (this.validH(x + dir[0], z + dir[1]))
251 sumHeight += this.height[x + dir[0]][z + dir[1]];
257 return sumHeight / count;
260 Map.prototype.getFullEntityList = function(rotateForMapExport = false)
262 // Change rotation from simple 2d to 3d befor giving to engine
263 if (rotateForMapExport)
264 for (let obj of this.objects)
265 obj.rotation.y = PI / 2 - obj.rotation.y;
267 // All non terrain objects
268 let entities = this.objects;
270 // Terrain objects e.g. trees
271 let size = this.size;
272 for (let x = 0; x < size; ++x)
273 for (let z = 0; z < size; ++z)
274 if (this.terrainObjects[x][z] !== undefined)
275 entities.push(this.terrainObjects[x][z]);
280 Map.prototype.getMapData = function()
284 // Convert 2D heightmap array to flat array
285 // Flat because it's easier to handle by the engine
286 let mapSize = this.size + 1;
287 let height = new Uint16Array(mapSize * mapSize);
288 for (let x = 0; x < mapSize; ++x)
289 for (let z = 0; z < mapSize; ++z)
292 if (TILE_CENTERED_HEIGHT_MAP)
293 currentHeight = this.cornerHeight(x, z);
295 currentHeight = this.height[x][z];
297 // Correct height by SEA_LEVEL and prevent under/overflow in terrain data
298 height[z * mapSize + x] = Math.max(0, Math.min(0xFFFF, Math.floor((currentHeight + SEA_LEVEL) * HEIGHT_UNITS_PER_METRE)));
301 data.height = height;
302 data.seaLevel = SEA_LEVEL;
304 // Terrain, map width in tiles
305 data.size = this.size;
307 // Get array of textures used in this map
308 data.textureNames = this.IDToName;
311 data.entities = this.getFullEntityList(true);
312 log("Number of entities: "+ data.entities.length);
314 // Convert 2D tile data to flat array
315 let tileIndex = new Uint16Array(this.size * this.size);
316 let tilePriority = new Uint16Array(this.size * this.size);
317 for (let x = 0; x < this.size; ++x)
318 for (let z = 0; z < this.size; ++z)
320 // TODO: For now just use the texture's index as priority, might want to do this another way
321 tileIndex[z * this.size + x] = this.texture[x][z];
322 tilePriority[z * this.size + x] = this.texture[x][z];
325 data.tileData = { "index": tileIndex, "priority": tilePriority };