Skip to content

Commit d092da0

Browse files
AzgaarCopilot
andcommitted
refactor: don't add fractal on edges
Co-authored-by: Copilot <copilot@github.com>
1 parent a6ed4eb commit d092da0

2 files changed

Lines changed: 42 additions & 30 deletions

File tree

src/renderers/coastline-fractal.ts

Lines changed: 28 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
135144
export 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;
Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,14 @@
1-
<g id="coastline"><g id="sea_island" opacity="0.5" stroke="#1f3846" stroke-width="0.5" filter="url(#dropShadow)" auto-filter="1" style="filter: none;"><use href="#feature_2" data-f="2"></use><use href="#feature_3" data-f="3"></use><use href="#feature_4" data-f="4"></use><use href="#feature_5" data-f="5"></use><use href="#feature_6" data-f="6"></use><use href="#feature_15" data-f="15"></use></g><g id="lake_island" opacity="1" stroke="#7c8eaf" stroke-width="0.35"></g></g>
1+
<g id="coastline"
2+
><g
3+
id="sea_island"
4+
opacity="0.5"
5+
stroke="#1f3846"
6+
stroke-width="0.5"
7+
filter="url(#dropShadow)"
8+
auto-filter="1"
9+
style="filter: none"
10+
><use href="#feature_2" data-f="2"></use><use href="#feature_3" data-f="3"></use
11+
><use href="#feature_4" data-f="4"></use><use href="#feature_5" data-f="5"></use
12+
><use href="#feature_6" data-f="6"></use><use href="#feature_15" data-f="15"></use></g
13+
><g id="lake_island" opacity="1" stroke="#7c8eaf" stroke-width="0.35"></g
14+
></g>

0 commit comments

Comments
 (0)