Remove rmgen euclidian distance helper function, refs rP20328 / D969.
[0ad.git] / binaries / data / mods / public / maps / random / rmgen / map.js
blob9326e949a612c48b4c6efcb39f90abd802ee86d2
1 /**
2  * Class for holding map data and providing basic API to change it
3  *
4  * @param {int} [size] - Size of the map in tiles
5  * @param {float} [baseHeight] - Starting height of the map
6  */
7 function Map(size, baseHeight)
9         // Size must be 0 to 1024, divisible by patches
10         this.size = size;
12         // Create 2D arrays for textures, object, and areas
13         this.texture = [];
14         this.terrainObjects = [];
15         this.area = [];
17         for (let i = 0; i < size; ++i)
18         {
19                 // Texture IDs
20                 this.texture[i] = new Uint16Array(size);
21                 // Entities
22                 this.terrainObjects[i] = [];
23                 // Area IDs
24                 this.area[i] = new Uint16Array(size);
26                 for (let j = 0; j < size; ++j)
27                         this.terrainObjects[i][j] = [];
28         }
30         // Create 2D array for heightmap
31         let mapSize = size;
32         if (!TILE_CENTERED_HEIGHT_MAP)
33                 ++mapSize;
35         this.height = [];
36         for (let i = 0; i < mapSize; ++i)
37         {
38                 this.height[i] = new Float32Array(mapSize);
40                 for (let j = 0; j < mapSize; ++j)
41                         this.height[i][j] = baseHeight;
42         }
44         // Create name <-> id maps for textures
45         this.nameToID = {};
46         this.IDToName = [];
48         // Array of objects (entitys/actors)
49         this.objects = [];
50         // Array of integers
51         this.tileClasses = [];
53         this.areaID = 0;
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];
72         // Add new texture
73         let id = this.IDToName.length;
74         this.nameToID[texture] = id;
75         this.IDToName[id] = texture;
77         return id;
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)
92         {
93                 let halfSize = Math.floor(this.size / 2);
94                 return Math.round(Math.euclidDistance2D(x, z, halfSize, halfSize)) < halfSize - distance - 1;
95         }
96         else
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)
109         if (x < 0 || z < 0)
110                 return false;
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)
172         terrain.place(x, z);
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);
193         if (!points)
194                 return undefined;
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;
201         painter.paint(area);
203         return area;
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));
222         return 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);
230         let xf = x - xi;
231         let zf = z - zi;
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)
244         let count = 0;
245         let sumHeight = 0;
247         for (let dir of [[-1, -1], [-1, 0], [0, -1], [0, 0]])
248                 if (this.validH(x + dir[0], z + dir[1]))
249                 {
250                         ++count;
251                         sumHeight += this.height[x + dir[0]][z + dir[1]];
252                 }
254         if (count == 0)
255                 return 0;
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]);
277         return entities;
280 Map.prototype.getMapData = function()
282         let data = {};
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)
290                 {
291                         let currentHeight;
292                         if (TILE_CENTERED_HEIGHT_MAP)
293                                 currentHeight = this.cornerHeight(x, z);
294                         else
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)));
299                 }
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;
310         // Entities
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)
319                 {
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];
323                 }
325         data.tileData = { "index": tileIndex, "priority": tilePriority };
327         return data;