Line data Source code
1 : /** 2 : * Generates a roughly circular clump of points. 3 : * 4 : * @param {number} size - The average number of points in the clump. Correlates to the area of the circle. 5 : * @param {number} coherence - How much the radius of the clump varies (1 = circle, 0 = very random). 6 : * @param {number} smoothness - How smooth the border of the clump is (1 = few "peaks", 0 = very jagged). 7 : * @param {number} [failfraction] - Percentage of place attempts allowed to fail. 8 : * @param {Vector2D} [centerPosition] - Tile coordinates of placer center. 9 : */ 10 : function ClumpPlacer(size, coherence, smoothness, failFraction = 0, centerPosition = undefined) 11 : { 12 0 : this.size = size; 13 0 : this.coherence = coherence; 14 0 : this.smoothness = smoothness; 15 0 : this.failFraction = failFraction; 16 0 : this.centerPosition = undefined; 17 : 18 0 : if (centerPosition) 19 0 : this.setCenterPosition(centerPosition); 20 : } 21 : 22 6 : ClumpPlacer.prototype.setCenterPosition = function(position) 23 : { 24 0 : this.centerPosition = deepfreeze(position.clone().round()); 25 : }; 26 : 27 6 : ClumpPlacer.prototype.place = function(constraint) 28 : { 29 : // Preliminary bounds check 30 0 : if (!g_Map.inMapBounds(this.centerPosition) || !constraint.allows(this.centerPosition)) 31 0 : return undefined; 32 : 33 0 : var points = []; 34 : 35 0 : var size = g_Map.getSize(); 36 0 : var gotRet = new Array(size).fill(0).map(p => new Uint8Array(size)); // booleans 37 0 : var radius = Math.sqrt(this.size / Math.PI); 38 0 : var perim = 4 * radius * 2 * Math.PI; 39 0 : var intPerim = Math.ceil(perim); 40 : 41 0 : var ctrlPts = 1 + Math.floor(1.0/Math.max(this.smoothness,1.0/intPerim)); 42 : 43 0 : if (ctrlPts > radius * 2 * Math.PI) 44 0 : ctrlPts = Math.floor(radius * 2 * Math.PI) + 1; 45 : 46 0 : var noise = new Float32Array(intPerim); //float32 47 0 : var ctrlCoords = new Float32Array(ctrlPts+1); //float32 48 0 : var ctrlVals = new Float32Array(ctrlPts+1); //float32 49 : 50 : // Generate some interpolated noise 51 0 : for (var i=0; i < ctrlPts; i++) 52 : { 53 0 : ctrlCoords[i] = i * perim / ctrlPts; 54 0 : ctrlVals[i] = randFloat(0, 2); 55 : } 56 : 57 0 : let c = 0; 58 0 : let looped = 0; 59 0 : for (let i = 0; i < intPerim; ++i) 60 : { 61 0 : if (ctrlCoords[(c+1) % ctrlPts] < i && !looped) 62 : { 63 0 : c = (c+1) % ctrlPts; 64 0 : if (c == ctrlPts-1) 65 0 : looped = 1; 66 : } 67 : 68 0 : noise[i] = cubicInterpolation( 69 : 1, 70 : (i - ctrlCoords[c]) / ((looped ? perim : ctrlCoords[(c + 1) % ctrlPts]) - ctrlCoords[c]), 71 : ctrlVals[(c + ctrlPts - 1) % ctrlPts], 72 : ctrlVals[c], 73 : ctrlVals[(c + 1) % ctrlPts], 74 : ctrlVals[(c + 2) % ctrlPts]); 75 : } 76 : 77 0 : let failed = 0; 78 0 : let count = 0; 79 0 : for (let stepAngle = 0; stepAngle < intPerim; ++stepAngle) 80 : { 81 0 : let position = this.centerPosition.clone(); 82 0 : let radiusUnitVector = new Vector2D(0, 1).rotate(-2 * Math.PI * stepAngle / perim); 83 0 : let maxRadiusSteps = Math.ceil(radius * (1 + (1 - this.coherence) * noise[stepAngle])); 84 : 85 0 : count += maxRadiusSteps; 86 0 : for (let stepRadius = 0; stepRadius < maxRadiusSteps; ++stepRadius) 87 : { 88 0 : let tilePos = position.clone().floor(); 89 : 90 0 : if (g_Map.inMapBounds(tilePos) && constraint.allows(tilePos)) 91 : { 92 0 : if (!gotRet[tilePos.x][tilePos.y]) 93 : { 94 0 : gotRet[tilePos.x][tilePos.y] = 1; 95 0 : points.push(tilePos); 96 : } 97 : } 98 : else 99 0 : ++failed; 100 : 101 0 : position.add(radiusUnitVector); 102 : } 103 : } 104 : 105 0 : return failed > count * this.failFraction ? undefined : points; 106 : };