Line data Source code
1 : /** 2 : * Absolute height change. 3 : */ 4 6 : const ELEVATION_SET = 0; 5 : 6 : /** 7 : * Relative height change. 8 : */ 9 6 : const ELEVATION_MODIFY = 1; 10 : 11 : /** 12 : * Sets the elevation of the Area in dependence to the given blendRadius and 13 : * interpolates it with the existing elevation. 14 : * 15 : * @param type - ELEVATION_MODIFY or ELEVATION_SET. 16 : * @param elevation - target height. 17 : * @param blendRadius - How steep the elevation change is. 18 : * @param randomElevation - maximum random elevation difference added to each vertex. 19 : */ 20 : function SmoothElevationPainter(type, elevation, blendRadius, randomElevation = 0) 21 : { 22 0 : this.type = type; 23 0 : this.elevation = elevation; 24 0 : this.blendRadius = blendRadius; 25 0 : this.randomElevation = randomElevation; 26 : 27 0 : if (type != ELEVATION_SET && type != ELEVATION_MODIFY) 28 0 : throw new Error("SmoothElevationPainter: invalid type '" + type + "'"); 29 : } 30 : 31 6 : SmoothElevationPainter.prototype.paint = function(area) 32 : { 33 : // The heightmap grid has one more vertex per side than the tile grid 34 0 : let heightmapSize = g_Map.height.length; 35 : 36 : // Remember height inside the area before changing it 37 0 : let gotHeightPt = []; 38 0 : let newHeight = []; 39 0 : for (let i = 0; i < heightmapSize; ++i) 40 : { 41 0 : gotHeightPt[i] = new Uint8Array(heightmapSize); 42 0 : newHeight[i] = new Float32Array(heightmapSize); 43 : } 44 : 45 : // Get heightmap grid vertices within or adjacent to the area 46 0 : let brushSize = 2; 47 0 : let heightPoints = []; 48 0 : for (let point of area.getPoints()) 49 0 : for (let dx = -1; dx < 1 + brushSize; ++dx) 50 : { 51 0 : let nx = point.x + dx; 52 0 : for (let dz = -1; dz < 1 + brushSize; ++dz) 53 : { 54 0 : let nz = point.y + dz; 55 0 : let position = new Vector2D(nx, nz); 56 : 57 0 : if (g_Map.validHeight(position) && !gotHeightPt[nx][nz]) 58 : { 59 0 : newHeight[nx][nz] = g_Map.getHeight(position); 60 0 : gotHeightPt[nx][nz] = 1; 61 0 : heightPoints.push(position); 62 : } 63 : } 64 : } 65 : 66 0 : let withinArea = (area, position) => g_TileVertices.some(vertexPos => area.contains(Vector2D.sub(position, vertexPos))); 67 : 68 : // Change height inside the area depending on the distance to the border 69 0 : breadthFirstSearchPaint({ 70 : "area": area, 71 : "brushSize": brushSize, 72 : "gridSize": heightmapSize, 73 : "withinArea": withinArea, 74 : "paintTile": (point, distance) => { 75 0 : let a = 1; 76 0 : if (distance <= this.blendRadius) 77 0 : a = (distance - 1) / this.blendRadius; 78 : 79 0 : if (this.type == ELEVATION_SET) 80 0 : newHeight[point.x][point.y] = (1 - a) * g_Map.getHeight(point); 81 : 82 0 : newHeight[point.x][point.y] += a * this.elevation + randFloat(-0.5, 0.5) * this.randomElevation; 83 : } 84 : }); 85 : 86 : // Smooth everything out 87 0 : for (let point of heightPoints) 88 : { 89 0 : if (!withinArea(area, point)) 90 0 : continue; 91 : 92 0 : let count = 0; 93 0 : let sum = 0; 94 : 95 0 : for (let dx = -1; dx <= 1; ++dx) 96 : { 97 0 : let nx = point.x + dx; 98 : 99 0 : for (let dz = -1; dz <= 1; ++dz) 100 : { 101 0 : let nz = point.y + dz; 102 : 103 0 : if (g_Map.validHeight(new Vector2D(nx, nz))) 104 : { 105 0 : sum += newHeight[nx][nz]; 106 0 : ++count; 107 : } 108 : } 109 : } 110 : 111 0 : g_Map.setHeight(point, (newHeight[point.x][point.y] + sum / count) / 2); 112 : } 113 : }; 114 : 115 : /** 116 : * Calls the given paintTile function on all points within the given Area, 117 : * providing the distance to the border of the area (1 for points on the border). 118 : * This function can traverse any grid, for instance the tile grid or the larger heightmap grid. 119 : * 120 : * @property area - An Area storing the set of points on the tile grid. 121 : * @property gridSize - The size of the grid to be traversed. 122 : * @property brushSize - Number of points per axis on the grid that are considered a point on the tilemap. 123 : * @property withinArea - Whether a point of the grid is considered part of the Area. 124 : * @property paintTile - Called for each point of the Area of the tile grid. 125 : */ 126 : function breadthFirstSearchPaint(args) 127 : { 128 : // These variables save which points were visited already and the shortest distance to the area 129 1 : let saw = []; 130 1 : let dist = []; 131 1 : for (let i = 0; i < args.gridSize; ++i) 132 : { 133 512 : saw[i] = new Uint8Array(args.gridSize); 134 512 : dist[i] = new Uint16Array(args.gridSize); 135 : } 136 : 137 1170 : let withinGrid = (x, z) => Math.min(x, z) >= 0 && Math.max(x, z) < args.gridSize; 138 : 139 : // Find all points outside of the area, mark them as seen and set zero distance 140 1 : let pointQueue = []; 141 1 : for (let point of args.area.getPoints()) 142 : // The brushSize is added because the entire brushSize is by definition part of the area 143 49 : for (let dx = -1; dx < 1 + args.brushSize; ++dx) 144 : { 145 147 : let nx = point.x + dx; 146 147 : for (let dz = -1; dz < 1 + args.brushSize; ++dz) 147 : { 148 441 : let nz = point.y + dz; 149 441 : let position = new Vector2D(nx, nz); 150 : 151 441 : if (!withinGrid(nx, nz) || args.withinArea(args.area, position) || saw[nx][nz]) 152 409 : continue; 153 : 154 32 : saw[nx][nz] = 1; 155 32 : dist[nx][nz] = 0; 156 32 : pointQueue.push(position); 157 : } 158 : } 159 : 160 : // Visit these points, then direct neighbors of them, then their neighbors recursively. 161 : // Call the paintTile method for each point within the area, with distance == 1 for the border. 162 1 : while (pointQueue.length) 163 : { 164 81 : let point = pointQueue.shift(); 165 81 : let distance = dist[point.x][point.y]; 166 : 167 81 : if (args.withinArea(args.area, point)) 168 49 : args.paintTile(point, distance); 169 : 170 : // Enqueue neighboring points 171 81 : for (let dx = -1; dx <= 1; ++dx) 172 : { 173 243 : let nx = point.x + dx; 174 243 : for (let dz = -1; dz <= 1; ++dz) 175 : { 176 729 : let nz = point.y + dz; 177 729 : let position = new Vector2D(nx, nz); 178 : 179 729 : if (!withinGrid(nx, nz) || !args.withinArea(args.area, position) || saw[nx][nz]) 180 680 : continue; 181 : 182 49 : saw[nx][nz] = 1; 183 49 : dist[nx][nz] = distance + 1; 184 49 : pointQueue.push(position); 185 : } 186 : } 187 : } 188 : }