@@ -132,10 +132,19 @@ export interface FractalizedShape {
132132 origIndices : number [ ] ; // index in points[] where original vertex i lives
133133}
134134
135+ export function fractalizeCoastline (
136+ points : [ number , number ] [ ] ,
137+ featureIndex : number
138+ ) : FractalizedShape {
139+ if ( points . length < 3 ) return { points, origIndices : points . map ( ( _ , i ) => i ) } ;
140+ const rand = Alea ( `${ seed } _c${ featureIndex } ` ) ;
141+ return fractalize ( points , rand , coastSettings ) ;
142+ }
143+
135144export function fractalize (
136145 points : [ number , number ] [ ] ,
137146 rand : ( ) => number ,
138- settings : CoastlineSettings ,
147+ settings : CoastlineSettings
139148) : FractalizedShape {
140149 const profile = makeRoughnessProfile ( rand , settings . roughnessContrast ) ;
141150
@@ -145,17 +154,13 @@ export function fractalize(
145154 for ( let i = 0 ; i < n ; i ++ ) {
146155 const [ x0 , y0 ] = points [ i ] ;
147156 const [ x1 , y1 ] = points [ ( i + 1 ) % n ] ;
148- const dx = x1 - x0 ,
149- dy = y1 - y0 ;
157+ const dx = x1 - x0 ;
158+ const dy = y1 - y0 ;
150159 segLens [ i ] = Math . sqrt ( dx * dx + dy * dy ) ;
151160 total += segLens [ i ] ;
152161 }
153162
154- // Degenerate polygon: all vertices are coincident after simplify/clip.
155- // Avoid Infinity/NaN in tParams by returning the input unchanged.
156- if ( total < 1e-9 ) {
157- return { points, origIndices : points . map ( ( _ , i ) => i ) } ;
158- }
163+ if ( total < 1e-9 ) return { points, origIndices : points . map ( ( _ , i ) => i ) } ; // exclude degenerate polygon
159164
160165 let cum = 0 ;
161166 const tParams = new Array < number > ( n ) ;
@@ -170,6 +175,8 @@ export function fractalize(
170175 for ( let i = 0 ; i < n ; i ++ ) {
171176 origIndices . push ( resultPts . length ) ;
172177 resultPts . push ( points [ i ] ) ;
178+ if ( isOnBorder ( points [ i ] ) ) continue ; // Skip fractal displacement for points on map border
179+
173180 const [ x0 , y0 ] = points [ i ] ;
174181 const [ x1 , y1 ] = points [ ( i + 1 ) % n ] ;
175182 subdivideEdge (
@@ -191,27 +198,19 @@ export function fractalize(
191198 return { points : resultPts , origIndices } ;
192199}
193200
194- export function fractalizeCoastline (
195- points : [ number , number ] [ ] ,
196- featureIndex : number ,
197- settings : CoastlineSettings = coastSettings ,
198- ) : FractalizedShape {
199- if ( points . length < 3 )
200- return { points, origIndices : points . map ( ( _ , i ) => i ) } ;
201- const rand = Alea ( `${ seed } _c${ featureIndex } ` ) ;
202- return fractalize ( points , rand , settings ) ;
201+ function isOnBorder ( [ x , y ] : [ number , number ] ) {
202+ return x === 0 || x === graphWidth || y === 0 || y === graphHeight ;
203203}
204204
205205/**
206206 * Build a closed SVG path string applying the correct curve algorithm per span:
207207 * Smooth span: Q midpoint B-spline — identical to curveBasisClosed. Produces flowing arcs that hide Voronoi angularity.
208208 * Jagged span: centripetal Catmull-Rom (α=0.5) through every fractal sub-point. Rounds sharp kinks into gentle curves.
209209 */
210- export function buildCoastlinePath ( shape : FractalizedShape ) : string {
211- const { points : pts , origIndices } = shape ;
212- const N = pts . length ;
210+ export function buildCoastlinePath ( { points, origIndices } : FractalizedShape ) : string {
211+ const N = points . length ;
213212 const M = origIndices . length ;
214- if ( M < 3 ) return "" ;
213+ if ( N < 3 || M < 3 ) return "" ;
215214
216215 const smooth : boolean [ ] = new Array ( M ) ;
217216 for ( let i = 0 ; i < M ; i ++ ) {
@@ -222,8 +221,8 @@ export function buildCoastlinePath(shape: FractalizedShape): string {
222221
223222 // Start at the B-spline midpoint of the last→first span when that span is
224223 // smooth so the closed loop is fully seamless; otherwise start at vertex 0.
225- const p0 = pts [ origIndices [ 0 ] ] ;
226- const pL = pts [ origIndices [ M - 1 ] ] ;
224+ const p0 = points [ origIndices [ 0 ] ] ;
225+ const pL = points [ origIndices [ M - 1 ] ] ;
227226 let atMid = smooth [ M - 1 ] ;
228227 const sx = atMid ? ( pL [ 0 ] + p0 [ 0 ] ) / 2 : p0 [ 0 ] ;
229228 const sy = atMid ? ( pL [ 1 ] + p0 [ 1 ] ) / 2 : p0 [ 1 ] ;
@@ -232,13 +231,13 @@ export function buildCoastlinePath(shape: FractalizedShape): string {
232231 for ( let i = 0 ; i < M ; i ++ ) {
233232 const ci = origIndices [ i ] ;
234233 const ni = origIndices [ ( i + 1 ) % M ] ;
235- const [ cpx , cpy ] = pts [ ci ] ;
234+ const [ cpx , cpy ] = points [ ci ] ;
236235
237236 if ( smooth [ i ] ) {
238237 // Q midpoint B-spline ≡ curveBasisClosed.
239238 // When arriving from a jagged span the cursor is already at cpx,cpy
240239 // so just line to the midpoint instead of emitting a degenerate Q.
241- const [ npx , npy ] = pts [ ni ] ;
240+ const [ npx , npy ] = points [ ni ] ;
242241 const mx = ( cpx + npx ) / 2 ;
243242 const my = ( cpy + npy ) / 2 ;
244243 d . push ( atMid ? `Q${ cpx } ,${ cpy } ${ mx } ,${ my } ` : `L${ mx } ,${ my } ` ) ;
@@ -250,10 +249,10 @@ export function buildCoastlinePath(shape: FractalizedShape): string {
250249 // Centripetal Catmull-Rom through every fractal sub-segment.
251250 const end = ni > ci ? ni : ni + N ;
252251 for ( let j = ci ; j < end ; j ++ ) {
253- const a = pts [ j % N ] ;
254- const b = pts [ ( j + 1 ) % N ] ;
255- const prev = pts [ ( j - 1 + N ) % N ] ;
256- const nnext = pts [ ( j + 2 ) % N ] ;
252+ const a = points [ j % N ] ;
253+ const b = points [ ( j + 1 ) % N ] ;
254+ const prev = points [ ( j - 1 + N ) % N ] ;
255+ const nnext = points [ ( j + 2 ) % N ] ;
257256 // Catmull-Rom tangents → Hermite control points (tension ≈ 0.25 for less radical curvature)
258257 const cp1x = a [ 0 ] + ( b [ 0 ] - prev [ 0 ] ) / 8 ;
259258 const cp1y = a [ 1 ] + ( b [ 1 ] - prev [ 1 ] ) / 8 ;
0 commit comments