Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen/area.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen/area.js (revision 20399) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/area.js (revision 20400) @@ -1,19 +1,14 @@ -/////////////////////////////////////////////////////////////////////////// -// Area -// -// Object representing a group of points/tiles -// -// points: Array of Point objects -// -/////////////////////////////////////////////////////////////////////////// +/** + * @file An Area is a set of points identified by an ID that was registered with the Map. + */ function Area(points, id) { - this.points = (points !== undefined ? points : []); + this.points = points; this.id = id; } Area.prototype.getID = function() { return this.id; }; Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen/constraint.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen/constraint.js (revision 20399) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/constraint.js (revision 20400) @@ -1,98 +1,98 @@ /** - * A Constraint decides if a tile satisfies a condition defined by the class. + * @file A Constraint decides if a tile satisfies a condition defined by the class. */ /** * The NullConstraint is always satisfied. */ function NullConstraint() {} NullConstraint.prototype.allows = function(x, z) { return true; }; /** * The AndConstraint is met if every given Constraint is satisfied by the tile. */ function AndConstraint(constraints) { this.constraints = constraints; } AndConstraint.prototype.allows = function(x, z) { return this.constraints.every(constraint => constraint.allows(x, z)); }; /** * The AvoidAreaConstraint is met if the tile is not part of the given Area. */ function AvoidAreaConstraint(area) { this.area = area; } AvoidAreaConstraint.prototype.allows = function(x, z) { return g_Map.area[x][z] != this.area.getID(); }; /** * The AvoidTextureConstraint is met if the terrain texture of the tile is different from the given texture. */ function AvoidTextureConstraint(textureID) { this.textureID = textureID; } AvoidTextureConstraint.prototype.allows = function(x, z) { return g_Map.texture[x][z] != this.textureID; }; /** * The AvoidTileClassConstraint is met if there are no tiles marked with the given TileClass within the given radius of the tile. */ function AvoidTileClassConstraint(tileClassID, distance) { this.tileClass = getTileClass(tileClassID); this.distance = distance; } AvoidTileClassConstraint.prototype.allows = function(x, z) { return this.tileClass.countMembersInRadius(x, z, this.distance) == 0; }; /** * The StayInTileClassConstraint is met if every tile within the given radius of the tile is marked with the given TileClass. */ function StayInTileClassConstraint(tileClassID, distance) { this.tileClass = getTileClass(tileClassID); this.distance = distance; } StayInTileClassConstraint.prototype.allows = function(x, z) { return this.tileClass.countNonMembersInRadius(x, z, this.distance) == 0; }; /** * The BorderTileClassConstraint is met if there are * tiles not marked with the given TileClass within distanceInside of the tile and * tiles marked with the given TileClass within distanceOutside of the tile. */ function BorderTileClassConstraint(tileClassID, distanceInside, distanceOutside) { this.tileClass = getTileClass(tileClassID); this.distanceInside = distanceInside; this.distanceOutside = distanceOutside; } BorderTileClassConstraint.prototype.allows = function(x, z) { return this.tileClass.countMembersInRadius(x, z, this.distanceOutside) > 0 && this.tileClass.countNonMembersInRadius(x, z, this.distanceInside) > 0; }; Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen/entity.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen/entity.js (revision 20399) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/entity.js (revision 20400) @@ -1,25 +1,25 @@ /** - * The Entity class stores the given template name, owner and location of an entity and assigns an entityID. + * @file The Entity class stores the given template name, owner and location of an entity and assigns an entityID. * Instances of this class (with the position using the tile coordinate system) are passed as such to the engine. * * @param orientation - rotation of this entity about the y-axis (up). */ // TODO: support full position and rotation function Entity(templateName, playerID, x, z, orientation = 0) { this.id = g_Map.getEntityID(); this.templateName = templateName; this.player = playerID; this.position = { "x": x, "y": 0, "z": z }; this.rotation = { "x": 0, "y": orientation, "z": 0 }; } Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen/environment.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen/environment.js (revision 20399) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/environment.js (revision 20400) @@ -1,153 +1,157 @@ +/** + * @file The environment settings govern the appearance of the Sky, Fog, Water and Post-Processing effects. + */ + var g_Environment = { "SkySet": "default", // textures for the skybox, subdirectory name of art/textures/skies "SunColor": { "r": 0.749020, "g": 0.749020, "b": 0.749020, "a": 0 }, // all rgb from 0 to 1 "SunElevation": 0.785398, // 0 to 2pi "SunRotation": 5.49779, // 0 to 2pi "TerrainAmbientColor": { "r": 0.501961, "g": 0.501961, "b": 0.501961, "a": 0 }, "UnitsAmbientColor": { "r": 0.501961, "g": 0.501961, "b": 0.501961, "a": 0 }, "Water": { "WaterBody": { "Type": "ocean", // Subdirectory name of art/textures/animated/water "Color": { "r": 0.3, "g": 0.35, "b": 0.7, "a": 0 }, "Tint": { "r": 0.28, "g": 0.3, "b": 0.59, "a": 0 }, "Height": 5, // TODO: The true default is 19.9, see ExportMap() in mapgen.js and SEA_LEVEL in library.js "Waviness": 8, // typically from 0 to 10 "Murkiness": 0.45, // 0 to 1, amount of tint to blend in with the refracted color "WindAngle": 0.0 // 0 to 2pi, direction of waves } }, "Fog": { "FogFactor": 0.0, "FogThickness": 0.5, "FogColor": { "r": 0.8, "g": 0.8, "b": 0.8, "a": 0 } }, "Postproc": { "Brightness": 0.0, "Contrast": 1.0, "Saturation": 1.0, "Bloom": 0.2, "PostprocEffect": "default" // "default", "hdr" or "DOF" } }; //////////////////////////////////////////////////////////////////////////// // Sun, Sky and Terrain //////////////////////////////////////////////////////////////////////////// function setSkySet(set) { g_Environment.SkySet = set; } function setSunColor(r, g, b) { g_Environment.SunColor = { "r" : r, "g" : g, "b" : b, "a" : 0 }; } function setSunElevation(e) { g_Environment.SunElevation = e; } function setSunRotation(r) { g_Environment.SunRotation = r; } function setTerrainAmbientColor(r, g, b) { g_Environment.TerrainAmbientColor = { "r" : r, "g" : g, "b" : b, "a" : 0 }; } function setUnitsAmbientColor(r, g, b) { g_Environment.UnitsAmbientColor = { "r" : r, "g" : g, "b" : b, "a" : 0 }; } //////////////////////////////////////////////////////////////////////////// // Water //////////////////////////////////////////////////////////////////////////// function setWaterColor(r, g, b) { g_Environment.Water.WaterBody.Color = { "r" : r, "g" : g, "b" : b, "a" : 0 }; } function setWaterTint(r, g, b) { g_Environment.Water.WaterBody.Tint = { "r" : r, "g" : g, "b" : b, "a" : 0 }; } function setWaterHeight(h) { g_Environment.Water.WaterBody.Height = h; WATER_LEVEL_CHANGED = true; } function setWaterWaviness(w) { g_Environment.Water.WaterBody.Waviness = w; } function setWaterMurkiness(m) { g_Environment.Water.WaterBody.Murkiness = m; } function setWaterType(m) { g_Environment.Water.WaterBody.Type = m; } function setWindAngle(m) { g_Environment.Water.WaterBody.WindAngle = m; } //////////////////////////////////////////////////////////////////////////// // Fog (numerical arguments between 0 and 1) //////////////////////////////////////////////////////////////////////////// function setFogFactor(s) { g_Environment.Fog.FogFactor = s / 100.0; } function setFogThickness(thickness) { g_Environment.Fog.FogThickness = thickness; } function setFogColor(r, g, b) { g_Environment.Fog.FogColor = { "r" : r, "g" : g, "b" : b, "a" : 0 }; } //////////////////////////////////////////////////////////////////////////// // Post Processing (numerical arguments between 0 and 1) //////////////////////////////////////////////////////////////////////////// function setPPBrightness(s) { g_Environment.Postproc.Brightness = s - 0.5; } function setPPContrast(s) { g_Environment.Postproc.Contrast = s + 0.5; } function setPPSaturation(s) { g_Environment.Postproc.Saturation = s * 2; } function setPPBloom(s) { g_Environment.Postproc.Bloom = (1 - s) * 0.2; } function setPPEffect(s) { g_Environment.Postproc.PostprocEffect = s; } Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen/group.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen/group.js (revision 20399) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/group.js (revision 20400) @@ -1,70 +1,70 @@ /** - * A Group tests if a set of entities specified in the constructor can be placed and + * @file A Group tests if a set of entities specified in the constructor can be placed and * potentially places some of them (typically all or none). * * The location is defined by the x and z property of the Group instance and can be modified externally. * The Group is free to determine whether, where exactly and how many entities to place. * * The Constraint to test against and the future owner of the entities are passed by the caller. * Typically Groups are called from createObjectGroup with the location set in the constructor or * from createObjectGroups that randomizes the x and z property of the Group before calling place. */ /** * Places all of the given Objects. * * @param objects - An array of Objects, for instance SimpleObjects. * @param avoidSelf - Objects will not overlap. * @param tileClass - Optional TileClass that tiles with placed entities are marked with. * @param x, z - The location the group is placed around. Can be omitted if the x and z properties are set externally. */ function SimpleGroup(objects, avoidSelf = false, tileClass = undefined, x = -1, z = -1) { this.objects = objects; this.tileClass = tileClass; this.avoidSelf = avoidSelf; this.x = x; this.z = z; } SimpleGroup.prototype.place = function(player, constraint) { let resultObjs = []; // Test if the Objects can be placed at the given location // Place none of them if one can't be placed. for (let object of this.objects) { let objs = object.place(this.x, this.z, player, this.avoidSelf, constraint); if (objs === undefined) return undefined; resultObjs = resultObjs.concat(objs); } // Add all objects to the map for (let obj of resultObjs) { if (g_Map.validT(obj.position.x, obj.position.z)) g_Map.addObject(obj); if (this.tileClass !== undefined) getTileClass(this.tileClass).add(Math.floor(obj.position.x), Math.floor(obj.position.z)); } return resultObjs; }; /** * Randomly choses one of the given Objects and places it just like the SimpleGroup. */ function RandomGroup(objects, avoidSelf = false, tileClass = undefined, x = -1, z = -1) { this.simpleGroup = new SimpleGroup([pickRandom(objects)], avoidSelf, tileClass, x, z); } RandomGroup.prototype.place = function(player, constraint) { return this.simpleGroup.place(player, constraint); }; Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen/map.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen/map.js (revision 20399) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/map.js (revision 20400) @@ -1,317 +1,317 @@ /** - * The Map stores the elevation grid, terrain textures and entities that are exported to the engine. + * @file The Map stores the elevation grid, terrain textures and entities that are exported to the engine. * * @param {Number} size - Radius or side length of the map in tiles * @param {Number} baseHeight - Initial elevation 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); // Area IDs this.area[i] = new Uint16Array(size); // Entities this.terrainObjects[i] = []; for (let j = 0; j < size; ++j) this.terrainObjects[i][j] = undefined; } // 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 Entities 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; } /** * Returns the ID of a texture name. * Creates a new ID if there isn't one assigned yet. */ Map.prototype.getTextureID = function(texture) { if (texture in this.nameToID) return this.nameToID[texture]; let id = this.IDToName.length; this.nameToID[texture] = id; this.IDToName[id] = texture; return id; }; /** * Returns the next unused entityID. */ Map.prototype.getEntityID = function() { return this.entityCount++; }; /** * Determines whether the given coordinates are within the given distance of the passable map area. * Should be used to restrict entity placement and path creation. */ Map.prototype.validT = function(x, z, distance = 0) { distance += MAP_BORDER_WIDTH; if (g_MapSettings.CircularMap) { let halfSize = Math.floor(this.size / 2); return Math.round(Math.euclidDistance2D(x, z, halfSize, halfSize)) < halfSize - distance - 1; } else return x >= distance && z >= distance && x < this.size - distance && z < this.size - distance; }; /** * Determines whether the given coordinates are within the tile grid, passable or not. * Should be used to restrict texture painting. */ Map.prototype.inMapBounds = function(x, z) { return x >= 0 && z >= 0 && x < this.size && z < this.size; }; /** * Determines whether the given coordinates are within the heightmap grid. * Should be used to restrict elevation changes. */ 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; }; /** * Tests if there is a tileclass with the given ID. */ Map.prototype.validClass = function(tileClassID) { return tileClassID >= 0 && tileClassID < this.tileClasses.length; }; /** * Returns the name of the texture of the given tile. */ Map.prototype.getTexture = function(x, z) { if (!this.validT(x, z)) throw new Error("getTexture: invalid tile position (" + x + ", " + z + ")"); return this.IDToName[this.texture[x][z]]; }; /** * Paints the given texture on the given tile. */ Map.prototype.setTexture = function(x, z, texture) { if (!this.validT(x, z)) throw new Error("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 new Error("getHeight: invalid vertex position (" + x + ", " + z + ")"); return this.height[x][z]; }; Map.prototype.setHeight = function(x, z, height) { if (!this.validH(x, z)) throw new Error("setHeight: invalid vertex position (" + x + ", " + z + ")"); this.height[x][z] = height; }; /** * Returns the Entity that was painted by a Terrain class on the given tile or undefined otherwise. */ Map.prototype.getTerrainObject = function(x, z) { if (!this.validT(x, z)) throw new Error("getTerrainObject: invalid tile position (" + x + ", " + z + ")"); return this.terrainObjects[x][z]; }; /** * Places the Entity on the given tile and allows to later replace it if the terrain was painted over. */ Map.prototype.setTerrainObject = function(x, z, object) { if (!this.validT(x, z)) throw new Error("setTerrainObject: invalid tile position (" + x + ", " + z + ")"); this.terrainObjects[x][z] = object; }; /** * Adds the given Entity to the map at the location it defines. */ Map.prototype.addObject = function(obj) { this.objects.push(obj); }; /** * Constructs a new Area object and informs the Map which points correspond to this area. */ Map.prototype.createArea = function(points) { let areaID = ++this.areaID; for (let p of points) this.area[p.x][p.z] = areaID; return new Area(points, areaID); }; /** * Returns an unused tileclass ID. */ Map.prototype.createTileClass = function() { let newID = this.tileClasses.length; this.tileClasses.push(new TileClass(this.size, newID)); return newID; }; /** * Retrieve interpolated height for arbitrary coordinates within the heightmap grid. */ 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; }; /** * Retrieve an array of all Entities placed on the map. */ Map.prototype.exportEntityList = function() { // Change rotation from simple 2d to 3d befor giving to engine for (let obj of this.objects) obj.rotation.y = Math.PI / 2 - obj.rotation.y; // Terrain objects e.g. trees for (let x = 0; x < this.size; ++x) for (let z = 0; z < this.size; ++z) if (this.terrainObjects[x][z]) this.objects.push(this.terrainObjects[x][z]); log("Number of entities: " + this.objects.length); return this.objects; }; /** * Convert the elevation grid to a one-dimensional array. */ Map.prototype.exportHeightData = function() { let heightmapSize = this.size + 1; let heightmap = new Uint16Array(Math.square(heightmapSize)); for (let x = 0; x < heightmapSize; ++x) for (let z = 0; z < heightmapSize; ++z) { let currentHeight = TILE_CENTERED_HEIGHT_MAP ? this.cornerHeight(x, z) : this.height[x][z]; // Correct height by SEA_LEVEL and prevent under/overflow in terrain data heightmap[z * heightmapSize + x] = Math.max(0, Math.min(0xFFFF, Math.floor((currentHeight + SEA_LEVEL) * HEIGHT_UNITS_PER_METRE))); } return heightmap; }; /** * Assemble terrain textures in a one-dimensional array. */ Map.prototype.exportTerrainTextures = function() { let tileIndex = new Uint16Array(Math.square(this.size)); let tilePriority = new Uint16Array(Math.square(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]; } return { "index": tileIndex, "priority": tilePriority }; }; Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen/object.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen/object.js (revision 20399) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/object.js (revision 20400) @@ -1,67 +1,67 @@ /** - * An Object tries to find locations around a location and returns an array of Entity items holding the template names, owners and locations on success. + * @file An Object tries to find locations around a location and returns an array of Entity items holding the template names, owners and locations on success. */ /** * The SimpleObject attempts to find locations for a random amount of entities with a random distance to the given center. */ function SimpleObject(templateName, minCount, maxCount, minDistance, maxDistance, minAngle = 0, maxAngle = 2 * Math.PI) { this.templateName = templateName; this.minCount = minCount; this.maxCount = maxCount; this.minDistance = minDistance; this.maxDistance = maxDistance; this.minAngle = minAngle; this.maxAngle = maxAngle; if (minCount > maxCount) throw new Error("SimpleObject: minCount should be less than or equal to maxCount"); if (minDistance > maxDistance) throw new Error("SimpleObject: minDistance should be less than or equal to maxDistance"); if (minAngle > maxAngle) throw new Error("SimpleObject: minAngle should be less than or equal to maxAngle"); } SimpleObject.prototype.place = function(centerX, centerZ, player, avoidSelf, constraint, maxFailCount = 20) { let entities = []; let failCount = 0; for (let i = 0; i < randIntInclusive(this.minCount, this.maxCount); ++i) while (true) { let distance = randFloat(this.minDistance, this.maxDistance); let angle = randFloat(0, 2 * Math.PI); let x = centerX + 0.5 + distance * Math.cos(angle); let z = centerZ + 0.5 + distance * Math.sin(angle); if (g_Map.validT(x, z) && (!avoidSelf || entities.every(ent => Math.euclidDistance2DSquared(x, z, ent.position.x, ent.position.z) >= 1)) && constraint.allows(Math.floor(x), Math.floor(z))) { entities.push(new Entity(this.templateName, player, x, z, randFloat(this.minAngle, this.maxAngle))); break; } else if (failCount++ > maxFailCount) return undefined; } return entities; }; /** * Same as SimpleObject, but choses one of the given templates at random. */ function RandomObject(templateNames, minCount, maxCount, minDistance, maxDistance, minAngle, maxAngle) { this.simpleObject = new SimpleObject(pickRandom(templateNames), minCount, maxCount, minDistance, maxDistance, minAngle, maxAngle); } RandomObject.prototype.place = function(centerX, centerZ, player, avoidSelf, constraint, maxFailCount = 20) { return this.simpleObject.place(centerX, centerZ, player, avoidSelf, constraint, maxFailCount); }; Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen/painter.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen/painter.js (revision 20399) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/painter.js (revision 20400) @@ -1,318 +1,318 @@ /** - * A Painter modifies an arbitrary feature in a given Area, for instance terrain textures, elevation or calling other painters on that Area. + * @file A Painter modifies an arbitrary feature in a given Area, for instance terrain textures, elevation or calling other painters on that Area. * Typically the area is determined by a Placer called from createArea or createAreas. */ /** * Marks the affected area with the given tileclass. */ function TileClassPainter(tileClass) { this.tileClass = tileClass; } TileClassPainter.prototype.paint = function(area) { for (let point of area.points) this.tileClass.add(point.x, point.z); }; /** * Removes the given tileclass from a given area. */ function TileClassUnPainter(tileClass) { this.tileClass = tileClass; } TileClassUnPainter.prototype.paint = function(area) { for (let point of area.points) this.tileClass.remove(point.x, point.z); }; /** * The MultiPainter applies several painters to the given area. */ function MultiPainter(painters) { this.painters = painters; } MultiPainter.prototype.paint = function(area) { for (let painter of this.painters) painter.paint(area); }; /** * The TerrainPainter draws a given terrain texture over the given area. * When used with TERRAIN_SEPARATOR, an entity is placed on each tile. */ function TerrainPainter(terrain) { this.terrain = createTerrain(terrain); } TerrainPainter.prototype.paint = function(area) { for (let point of area.points) this.terrain.place(point.x, point.z); }; /** * The LayeredPainter sets different Terrains within the Area. * It choses the Terrain depending on the distance to the border of the Area. * * The Terrains given in the first array are painted from the border of the area towards the center (outermost first). * The widths array has one item less than the Terrains array. * Each width specifies how many tiles the corresponding Terrain should be wide (distance to the prior Terrain border). * The remaining area is filled with the last terrain. */ function LayeredPainter(terrainArray, widths) { if (!(terrainArray instanceof Array)) throw new Error("LayeredPainter: terrains must be an array!"); this.terrains = terrainArray.map(terrain => createTerrain(terrain)); this.widths = widths; } LayeredPainter.prototype.paint = function(area) { breadthFirstSearchPaint({ "area": area, "brushSize": 1, "gridSize": getMapSize(), "withinArea": (areaID, x, z) => g_Map.area[x][z] == areaID, "paintTile": (point, distance) => { let width = 0; let i = 0; for (; i < this.widths.length; ++i) { width += this.widths[i]; if (width >= distance) break; } this.terrains[i].place(point.x, point.z); } }); }; /** * Sets the given height in the given Area. */ function ElevationPainter(elevation) { this.elevation = elevation; } ElevationPainter.prototype.paint = function(area) { for (let point of area.points) for (let [dx, dz] of [[0, 0], [1, 0], [0, 1], [1, 1]]) { let x = point.x + dx; let z = point.z + dz; if (g_Map.inMapBounds(x, z)) g_Map.height[x][z] = this.elevation; } }; /** * Absolute height change. */ const ELEVATION_SET = 0; /** * Relative height change. */ const ELEVATION_MODIFY = 1; /** * Sets the elevation of the Area in dependence to the given blendRadius and * interpolates it with the existing elevation. * * @param type - ELEVATION_MODIFY or ELEVATION_SET. * @param elevation - target height. * @param blendRadius - How steep the elevation change is. * @param randomElevation - maximum random elevation difference added to each vertex. */ function SmoothElevationPainter(type, elevation, blendRadius, randomElevation = 0) { this.type = type; this.elevation = elevation; this.blendRadius = blendRadius; this.randomElevation = randomElevation; if (type != ELEVATION_SET && type != ELEVATION_MODIFY) throw new Error("SmoothElevationPainter: invalid type '" + type + "'"); } SmoothElevationPainter.prototype.paint = function(area) { // The heightmap grid has one more vertex per side than the tile grid let heightmapSize = g_Map.height.length; // Remember height inside the area before changing it let gotHeightPt = []; let newHeight = []; for (let i = 0; i < heightmapSize; ++i) { gotHeightPt[i] = new Uint8Array(heightmapSize); newHeight[i] = new Float32Array(heightmapSize); } // Get heightmap grid vertices within or adjacent to the area let brushSize = 2; let heightPoints = []; for (let point of area.points) for (let dx = -1; dx < 1 + brushSize; ++dx) { let nx = point.x + dx; for (let dz = -1; dz < 1 + brushSize; ++dz) { let nz = point.z + dz; if (g_Map.validH(nx, nz) && !gotHeightPt[nx][nz]) { newHeight[nx][nz] = g_Map.height[nx][nz]; gotHeightPt[nx][nz] = 1; heightPoints.push({ "x": nx, "z": nz }); } } } // Every vertex of a tile is considered within the area let withinArea = (areaID, x, z) => { for (let [dx, dz] of [[0, 0], [1, 0], [0, 1], [1, 1]]) if (g_Map.inMapBounds(x - dx, z - dz) && g_Map.area[x - dx][z - dz] == areaID) return true; return false; }; // Change height inside the area depending on the distance to the border breadthFirstSearchPaint({ "area": area, "brushSize": brushSize, "gridSize": heightmapSize, "withinArea": withinArea, "paintTile": (point, distance) => { let a = 1; if (distance <= this.blendRadius) a = (distance - 1) / this.blendRadius; if (this.type == ELEVATION_SET) newHeight[point.x][point.z] = (1 - a) * g_Map.height[point.x][point.z]; newHeight[point.x][point.z] += a * this.elevation + randFloat(-0.5, 0.5) * this.randomElevation; } }); // Smooth everything out let areaID = area.getID(); for (let point of heightPoints) { if (!withinArea(areaID, point.x, point.z)) continue; let count = 0; let sum = 0; for (let dx = -1; dx <= 1; ++dx) { let nx = point.x + dx; for (let dz = -1; dz <= 1; ++dz) { let nz = point.z + dz; if (g_Map.validH(nx, nz)) { sum += newHeight[nx][nz]; ++count; } } } g_Map.height[point.x][point.z] = (newHeight[point.x][point.z] + sum / count) / 2; } }; /** * Calls the given paintTile function on all points within the given Area, * providing the distance to the border of the area (1 for points on the border). * This function can traverse any grid, for instance the tile grid or the larger heightmap grid. * * @property area - An Area storing the set of points on the tile grid. * @property gridSize - The size of the grid to be traversed. * @property brushSize - Number of points per axis on the grid that are considered a point on the tilemap. * @property withinArea - Whether a point of the grid is considered part of the Area. * @property paintTile - Called for each point of the Area of the tile grid. */ function breadthFirstSearchPaint(args) { // These variables save which points were visited already and the shortest distance to the area let saw = []; let dist = []; for (let i = 0; i < args.gridSize; ++i) { saw[i] = new Uint8Array(args.gridSize); dist[i] = new Uint16Array(args.gridSize); } let withinGrid = (x, z) => Math.min(x, z) >= 0 && Math.max(x, z) < args.gridSize; // Find all points outside of the area, mark them as seen and set zero distance let pointQueue = []; let areaID = args.area.getID(); for (let point of args.area.points) // The brushSize is added because the entire brushSize is by definition part of the area for (let dx = -1; dx < 1 + args.brushSize; ++dx) { let nx = point.x + dx; for (let dz = -1; dz < 1 + args.brushSize; ++dz) { let nz = point.z + dz; if (!withinGrid(nx, nz) || args.withinArea(areaID, nx, nz) || saw[nx][nz]) continue; saw[nx][nz] = 1; dist[nx][nz] = 0; pointQueue.push({ "x": nx, "z": nz }); } } // Visit these points, then direct neighbors of them, then their neighbors recursively. // Call the paintTile method for each point within the area, with distance == 1 for the border. while (pointQueue.length) { let point = pointQueue.shift(); let distance = dist[point.x][point.z]; if (args.withinArea(areaID, point.x, point.z)) args.paintTile(point, distance); // Enqueue neighboring points for (let dx = -1; dx <= 1; ++dx) { let nx = point.x + dx; for (let dz = -1; dz <= 1; ++dz) { let nz = point.z + dz; if (!withinGrid(nx, nz) || !args.withinArea(areaID, nx, nz) || saw[nx][nz]) continue; saw[nx][nz] = 1; dist[nx][nz] = distance + 1; pointQueue.push({ "x": nx, "z": nz }); } } } } Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen/placer_centered.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen/placer_centered.js (revision 20399) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/placer_centered.js (revision 20400) @@ -1,261 +1,261 @@ /** - * A Centered Placer generates a shape (array of points) around a variable center location satisfying a Constraint. + * @file A Centered Placer generates a shape (array of points) around a variable center location satisfying a Constraint. * The center is determined by the x and z property which can be modified externally, typically by createAreas. */ ///////////////////////////////////////////////////////////////////////////////////////// // ClumpPlacer // // Class for generating a roughly circular clump of points // // size: The average number of points in the clump // coherence: How much the radius of the clump varies (1.0 = circle, 0.0 = very random) // smoothness: How smooth the border of the clump is (1.0 = few "peaks", 0.0 = very jagged) // failfraction: Percentage of place attempts allowed to fail (optional) // x, z: Tile coordinates of placer center (optional) // ///////////////////////////////////////////////////////////////////////////////////////// function ClumpPlacer(size, coherence, smoothness, failFraction, x, z) { this.size = size; this.coherence = coherence; this.smoothness = smoothness; this.failFraction = failFraction !== undefined ? failFraction : 0; this.x = x !== undefined ? x : -1; this.z = z !== undefined ? z : -1; } ClumpPlacer.prototype.place = function(constraint) { // Preliminary bounds check if (!g_Map.inMapBounds(this.x, this.z) || !constraint.allows(this.x, this.z)) return undefined; var retVec = []; var size = getMapSize(); var gotRet = new Array(size).fill(0).map(p => new Uint8Array(size)); // booleans var radius = sqrt(this.size / PI); var perim = 4 * radius * 2 * PI; var intPerim = ceil(perim); var ctrlPts = 1 + Math.floor(1.0/Math.max(this.smoothness,1.0/intPerim)); if (ctrlPts > radius * 2 * PI) ctrlPts = Math.floor(radius * 2 * PI) + 1; var noise = new Float32Array(intPerim); //float32 var ctrlCoords = new Float32Array(ctrlPts+1); //float32 var ctrlVals = new Float32Array(ctrlPts+1); //float32 // Generate some interpolated noise for (var i=0; i < ctrlPts; i++) { ctrlCoords[i] = i * perim / ctrlPts; ctrlVals[i] = randFloat(0, 2); } var c = 0; var looped = 0; for (var i=0; i < intPerim; i++) { if (ctrlCoords[(c+1) % ctrlPts] < i && !looped) { c = (c+1) % ctrlPts; if (c == ctrlPts-1) looped = 1; } noise[i] = cubicInterpolation( 1, (i - ctrlCoords[c]) / ((looped ? perim : ctrlCoords[(c + 1) % ctrlPts]) - ctrlCoords[c]), ctrlVals[(c + ctrlPts - 1) % ctrlPts], ctrlVals[c], ctrlVals[(c + 1) % ctrlPts], ctrlVals[(c + 2) % ctrlPts]); } var failed = 0; for (var p=0; p < intPerim; p++) { var th = 2 * PI * p / perim; var r = radius * (1 + (1-this.coherence)*noise[p]); var s = sin(th); var c = cos(th); var xx = this.x; var yy = this.z; for (var k=0; k < ceil(r); k++) { var i = Math.floor(xx); var j = Math.floor(yy); if (g_Map.inMapBounds(i, j) && constraint.allows(i, j)) { if (!gotRet[i][j]) { // Only include each point once gotRet[i][j] = 1; retVec.push({ "x": i, "z": j }); } } else failed++; xx += s; yy += c; } } return failed > this.size * this.failFraction ? undefined : retVec; }; ///////////////////////////////////////////////////////////////////////////////////////// // Chain Placer // // Class for generating a more random clump of points it randomly creates circles around the edges of the current clump // // minRadius: minimum radius of the circles // maxRadius: maximum radius of the circles // numCircles: the number of the circles // failfraction: Percentage of place attempts allowed to fail (optional) // x, z: Tile coordinates of placer center (optional) // fcc: Farthest circle center (optional) // q: a list containing numbers. each time if the list still contains values, pops one from the end and uses it as the radius (optional) // ///////////////////////////////////////////////////////////////////////////////////////// function ChainPlacer(minRadius, maxRadius, numCircles, failFraction, x, z, fcc, q) { this.minRadius = minRadius; this.maxRadius = maxRadius; this.numCircles = numCircles; this.failFraction = failFraction !== undefined ? failFraction : 0; this.x = x !== undefined ? x : -1; this.z = z !== undefined ? z : -1; this.fcc = fcc !== undefined ? fcc : 0; this.q = q !== undefined ? q : []; } ChainPlacer.prototype.place = function(constraint) { // Preliminary bounds check if (!g_Map.inMapBounds(this.x, this.z) || !constraint.allows(this.x, this.z)) return undefined; var retVec = []; var size = getMapSize(); var failed = 0, count = 0; var queueEmpty = !this.q.length; var gotRet = new Array(size).fill(0).map(p => new Array(size).fill(-1)); --size; this.minRadius = Math.min(this.maxRadius, Math.max(this.minRadius, 1)); var edges = [[this.x, this.z]]; for (var i = 0; i < this.numCircles; ++i) { var [cx, cz] = pickRandom(edges); if (queueEmpty) var radius = randIntInclusive(this.minRadius, this.maxRadius); else { var radius = this.q.pop(); queueEmpty = !this.q.length; } var sx = cx - radius, lx = cx + radius; var sz = cz - radius, lz = cz + radius; sx = Math.max(0, sx); sz = Math.max(0, sz); lx = Math.min(lx, size); lz = Math.min(lz, size); var radius2 = radius * radius; var dx, dz; for (var ix = sx; ix <= lx; ++ix) for (var iz = sz; iz <= lz; ++ iz) { dx = ix - cx; dz = iz - cz; if (dx * dx + dz * dz <= radius2) { if (g_Map.inMapBounds(ix, iz) && constraint.allows(ix, iz)) { var state = gotRet[ix][iz]; if (state == -1) { retVec.push({ "x": ix, "z": iz }); gotRet[ix][iz] = -2; } else if (state >= 0) { var s = edges.splice(state, 1); gotRet[ix][iz] = -2; var edgesLength = edges.length; for (var k = state; k < edges.length; ++k) --gotRet[edges[k][0]][edges[k][1]]; } } else ++failed; ++count; } } for (var ix = sx; ix <= lx; ++ix) for (var iz = sz; iz <= lz; ++ iz) { if (this.fcc) if ((this.x - ix) > this.fcc || (ix - this.x) > this.fcc || (this.z - iz) > this.fcc || (iz - this.z) > this.fcc) continue; if (gotRet[ix][iz] == -2) { if (ix > 0) { if (gotRet[ix-1][iz] == -1) { edges.push([ix, iz]); gotRet[ix][iz] = edges.length - 1; continue; } } if (iz > 0) { if (gotRet[ix][iz-1] == -1) { edges.push([ix, iz]); gotRet[ix][iz] = edges.length - 1; continue; } } if (ix < size) { if (gotRet[ix+1][iz] == -1) { edges.push([ix, iz]); gotRet[ix][iz] = edges.length - 1; continue; } } if (iz < size) { if (gotRet[ix][iz+1] == -1) { edges.push([ix, iz]); gotRet[ix][iz] = edges.length - 1; continue; } } } } } return failed > count * this.failFraction ? undefined : retVec; }; Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen/placer_noncentered.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen/placer_noncentered.js (revision 20399) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/placer_noncentered.js (revision 20400) @@ -1,259 +1,259 @@ /** - * A Non-Centered Placer generates an shape (array of points) at a fixed location meeting a Constraint and + * @file A Non-Centered Placer generates an shape (array of points) at a fixed location meeting a Constraint and * is typically called by createArea. * Since this type of Placer has no x and z property, its location cannot be randomized using createAreas. */ /** * The RectPlacer provides the points between (x1, z1) and (x2, z2) if they meet the Constraint. */ function RectPlacer(x1, z1, x2, z2) { this.x1 = x1; this.z1 = z1; this.x2 = x2; this.z2 = z2; if (x1 > x2 || z1 > z2) throw new Error("RectPlacer: invalid bounds"); } RectPlacer.prototype.place = function(constraint) { if (!g_Map.inMapBounds(this.x1, this.z1) || !g_Map.inMapBounds(this.x2, this.z2)) return undefined; let points = []; for (let x = this.x1; x <= this.x2; ++x) for (let z = this.z1; z <= this.z2; ++z) if (constraint.allows(x, z)) points.push({ "x": x, "z": z }); else return undefined; return points; }; /** * The HeightPlacer provides all points between the minimum and maximum elevation that meet the Constraint. */ function HeightPlacer(minElevation, maxElevation) { this.minElevation = minElevation; this.maxElevation = maxElevation; } HeightPlacer.prototype.place = function(constraint) { let mapSize = getMapSize(); let points = []; for (let x = 0; x < mapSize; ++x) for (let z = 0; z < mapSize; ++z) if (g_Map.height[x][z] >= this.minElevation && g_Map.height[x][z] <= this.maxElevation && constraint.allows(x, z)) points.push({ "x": x, "z": z }); return points; }; ///////////////////////////////////////////////////////////////////////////////////////// // PathPlacer // // Class for creating a winding path between two points // // x1,z1: Starting point of path // x2,z2: Ending point of path // width: Width of the path // a: Waviness - how wavy the path will be (higher is wavier, 0.0 is straight line) // b: Smoothness - how smooth the path will be (higher is smoother) // c: Offset - max amplitude of waves along the path (0.0 is straight line) // taper: Tapering - how much the width of the path changes from start to end // if positive, the width will decrease by that factor, if negative the width // will increase by that factor // ///////////////////////////////////////////////////////////////////////////////////////// function PathPlacer(x1, z1, x2, z2, width, a, b, c, taper, failfraction) { this.x1 = x1; this.z1 = z1; this.x2 = x2; this.z2 = z2; this.width = width; this.a = a; this.b = b; this.c = c; this.taper = taper; this.failfraction = (failfraction !== undefined ? failfraction : 5); } PathPlacer.prototype.place = function(constraint) { /*/ Preliminary bounds check if (!g_Map.validT(this.x1, this.z1) || !constraint.allows(this.x1, this.z1) || !g_Map.validT(this.x2, this.z2) || !constraint.allows(this.x2, this.z2)) { return undefined; }*/ var failed = 0; var dx = (this.x2 - this.x1); var dz = (this.z2 - this.z1); var dist = Math.sqrt(dx*dx + dz*dz); dx /= dist; dz /= dist; var numSteps = 1 + Math.floor(dist/4 * this.a); var numISteps = 1 + Math.floor(dist/4 * this.b); var totalSteps = numSteps*numISteps; var offset = 1 + Math.floor(dist/4 * this.c); var size = getMapSize(); var gotRet = []; for (var i = 0; i < size; ++i) gotRet[i] = new Uint8Array(size); // bool / uint8 // Generate random offsets var ctrlVals = new Float32Array(numSteps); //float32 for (var j = 1; j < (numSteps-1); ++j) { ctrlVals[j] = randFloat(-offset, offset); } // Interpolate for smoothed 1D noise var noise = new Float32Array(totalSteps+1); //float32 for (var j = 0; j < numSteps; ++j) for (let k = 0; k < numISteps; ++k) noise[j * numISteps + k] = cubicInterpolation( 1, k / numISteps, ctrlVals[(j + numSteps - 1) % numSteps], ctrlVals[j], ctrlVals[(j + 1) % numSteps], ctrlVals[(j + 2) % numSteps]); var halfWidth = 0.5 * this.width; // Add smoothed noise to straight path var segments1 = []; var segments2 = []; for (var j = 0; j < totalSteps; ++j) { // Interpolated points along straight path var t = j/totalSteps; var tx = this.x1 * (1.0 - t) + this.x2 * t; var tz = this.z1 * (1.0 - t) + this.z2 * t; var t2 = (j+1)/totalSteps; var tx2 = this.x1 * (1.0 - t2) + this.x2 * t2; var tz2 = this.z1 * (1.0 - t2) + this.z2 * t2; // Find noise offset points var nx = (tx - dz * noise[j]); var nz = (tz + dx * noise[j]); var nx2 = (tx2 - dz * noise[j+1]); var nz2 = (tz2 + dx * noise[j+1]); // Find slope of offset points var ndx = (nx2 - nx); var ndz = (nz2 - nz); var dist = Math.sqrt(ndx*ndx + ndz*ndz); ndx /= dist; ndz /= dist; var taperedWidth = (1.0 - t*this.taper) * halfWidth; // Find slope of offset path var px = Math.round(nx - ndz * -taperedWidth); var pz = Math.round(nz + ndx * -taperedWidth); segments1.push({ "x": px, "z": pz }); var px2 = Math.round(nx2 - ndz * taperedWidth); var pz2 = Math.round(nz2 + ndx * taperedWidth); segments2.push({ "x": px2, "z": pz2 }); } var retVec = []; // Draw path segments var num = segments1.length - 1; for (var j = 0; j < num; ++j) { // Fill quad formed by these 4 points // Note the code assumes these points have been rounded to integer values var pt11 = segments1[j]; var pt12 = segments1[j+1]; var pt21 = segments2[j]; var pt22 = segments2[j+1]; var tris = [[pt12, pt11, pt21], [pt12, pt21, pt22]]; for (var t = 0; t < 2; ++t) { // Sort vertices by min z var tri = tris[t].sort( function(a, b) { return a.z - b.z; } ); // Fills in a line from (z, x1) to (z,x2) var fillLine = function(z, x1, x2) { var left = Math.round(Math.min(x1, x2)); var right = Math.round(Math.max(x1, x2)); for (var x = left; x <= right; x++) { if (constraint.allows(x, z)) { if (g_Map.inMapBounds(x, z) && !gotRet[x][z]) { retVec.push({ "x": x, "z": z }); gotRet[x][z] = 1; } } else { failed++; } } }; var A = tri[0]; var B = tri[1]; var C = tri[2]; var dx1 = (B.z != A.z) ? ((B.x - A.x) / (B.z - A.z)) : 0; var dx2 = (C.z != A.z) ? ((C.x - A.x) / (C.z - A.z)) : 0; var dx3 = (C.z != B.z) ? ((C.x - B.x) / (C.z - B.z)) : 0; if (A.z == B.z) { fillLine(A.z, A.x, B.x); } else { for (var z = A.z; z <= B.z; z++) { fillLine(z, A.x + dx1*(z - A.z), A.x + dx2*(z - A.z)); } } if (B.z == C.z) { fillLine(B.z, B.x, C.x); } else { for (var z = B.z + 1; z < C.z; z++) { fillLine(z, B.x + dx3*(z - B.z), A.x + dx2*(z - A.z)); } } } } return ((failed > this.width*this.failfraction*dist) ? undefined : retVec); }; Index: ps/trunk/binaries/data/mods/public/maps/random/rmgen/terrain.js =================================================================== --- ps/trunk/binaries/data/mods/public/maps/random/rmgen/terrain.js (revision 20399) +++ ps/trunk/binaries/data/mods/public/maps/random/rmgen/terrain.js (revision 20400) @@ -1,44 +1,44 @@ /** - * A Terrain is a class that modifies an arbitrary property of a given tile. + * @file A Terrain is a class that modifies an arbitrary property of a given tile. */ /** * SimpleTerrain paints the given texture on the terrain. * * Optionally it places an entity on the affected tiles and * replaces prior entities added by SimpleTerrain on the same tile. */ function SimpleTerrain(texture, templateName = undefined) { if (texture === undefined) throw new Error("SimpleTerrain: texture not defined"); this.texture = texture; this.templateName = templateName; } SimpleTerrain.prototype.place = function(x, z) { if (g_Map.validT(x, z)) g_Map.terrainObjects[x][z] = this.templateName ? new Entity(this.templateName, 0, x + 0.5, z + 0.5, randFloat(0, 2 * Math.PI)) : undefined; g_Map.texture[x][z] = g_Map.getTextureID(this.texture); }; /** * RandomTerrain places one of the given Terrains on the tile. * It choses a random Terrain each tile. * This is commonly used to create heterogeneous forests. */ function RandomTerrain(terrains) { if (!(terrains instanceof Array) || !terrains.length) throw new Error("RandomTerrain: Invalid terrains array"); this.terrains = terrains; } RandomTerrain.prototype.place = function(x, z) { pickRandom(this.terrains).place(x, z); };