Line data Source code
1 : /** 2 : * Creates a winding path between two points. 3 : * 4 : * @param {Vector2D} start - Starting position of the path. 5 : * @param {Vector2D} end - Endposition of the path. 6 : * @param {number} width - Number of tiles between two sides of the path. 7 : * @param {number} waviness - 0 is a straight line, higher numbers are. 8 : * @param {number} smoothness - the higher the number, the smoother the path. 9 : * @param {number} offset - Maximum amplitude of waves along the path. 0 is straight line. 10 : * @param {number} tapering - How much the width of the path changes from start to end. 11 : * If positive, the width will decrease by that factor. 12 : * If negative the width will increase by that factor. 13 : */ 14 : function PathPlacer(start, end, width, waviness, smoothness, offset, tapering, failFraction = 0) 15 : { 16 0 : this.start = start; 17 0 : this.end = end; 18 0 : this.width = width; 19 0 : this.waviness = waviness; 20 0 : this.smoothness = smoothness; 21 0 : this.offset = offset; 22 0 : this.tapering = tapering; 23 0 : this.failFraction = failFraction; 24 : } 25 : 26 6 : PathPlacer.prototype.place = function(constraint) 27 : { 28 0 : let pathLength = this.start.distanceTo(this.end); 29 : 30 0 : let numStepsWaviness = 1 + Math.floor(pathLength / 4 * this.waviness); 31 0 : let numStepsLength = 1 + Math.floor(pathLength / 4 * this.smoothness); 32 0 : let offset = 1 + Math.floor(pathLength / 4 * this.offset); 33 : 34 : // Generate random offsets 35 0 : let ctrlVals = new Float32Array(numStepsWaviness); 36 0 : for (let j = 1; j < numStepsWaviness - 1; ++j) 37 0 : ctrlVals[j] = randFloat(-offset, offset); 38 : 39 : // Interpolate for smoothed 1D noise 40 0 : let totalSteps = numStepsWaviness * numStepsLength; 41 0 : let noise = new Float32Array(totalSteps + 1); 42 0 : for (let j = 0; j < numStepsWaviness; ++j) 43 0 : for (let k = 0; k < numStepsLength; ++k) 44 0 : noise[j * numStepsLength + k] = cubicInterpolation( 45 : 1, 46 : k / numStepsLength, 47 : ctrlVals[(j + numStepsWaviness - 1) % numStepsWaviness], 48 : ctrlVals[j], 49 : ctrlVals[(j + 1) % numStepsWaviness], 50 : ctrlVals[(j + 2) % numStepsWaviness]); 51 : 52 : // Add smoothed noise to straight path 53 0 : let pathPerpendicular = Vector2D.sub(this.end, this.start).normalize().perpendicular(); 54 0 : let segments1 = []; 55 0 : let segments2 = []; 56 : 57 0 : for (let j = 0; j < totalSteps; ++j) 58 : { 59 : // Interpolated points along straight path 60 0 : let step1 = j / totalSteps; 61 0 : let step2 = (j + 1) / totalSteps; 62 0 : let stepStart = Vector2D.add(Vector2D.mult(this.start, 1 - step1), Vector2D.mult(this.end, step1)); 63 0 : let stepEnd = Vector2D.add(Vector2D.mult(this.start, 1 - step2), Vector2D.mult(this.end, step2)); 64 : 65 : // Find noise offset points 66 0 : let noiseStart = Vector2D.add(stepStart, Vector2D.mult(pathPerpendicular, noise[j])); 67 0 : let noiseEnd = Vector2D.add(stepEnd, Vector2D.mult(pathPerpendicular, noise[j + 1])); 68 0 : let noisePerpendicular = Vector2D.sub(noiseEnd, noiseStart).normalize().perpendicular(); 69 : 70 0 : let taperedWidth = (1 - step1 * this.tapering) * this.width / 2; 71 : 72 0 : segments1.push(Vector2D.sub(noiseStart, Vector2D.mult(noisePerpendicular, taperedWidth)).round()); 73 0 : segments2.push(Vector2D.add(noiseEnd, Vector2D.mult(noisePerpendicular, taperedWidth)).round()); 74 : } 75 : 76 : // Draw path segments 77 0 : let size = g_Map.getSize(); 78 0 : let gotRet = new Array(size).fill(0).map(i => new Uint8Array(size)); 79 0 : let retVec = []; 80 0 : let failed = 0; 81 : 82 0 : for (let j = 0; j < segments1.length - 1; ++j) 83 : { 84 0 : let points = new ConvexPolygonPlacer([segments1[j], segments1[j + 1], segments2[j], segments2[j + 1]], Infinity).place(new NullConstraint()); 85 0 : if (!points) 86 0 : continue; 87 : 88 0 : for (let point of points) 89 : { 90 0 : if (!constraint.allows(point)) 91 : { 92 0 : if (!this.failFraction) 93 0 : return undefined; 94 : 95 0 : ++failed; 96 0 : continue; 97 : } 98 : 99 0 : if (g_Map.inMapBounds(point) && !gotRet[point.x][point.y]) 100 : { 101 0 : retVec.push(point); 102 0 : gotRet[point.x][point.y] = 1; 103 : } 104 : } 105 : } 106 : 107 0 : return failed > this.failFraction * this.width * pathLength ? undefined : retVec; 108 : };