Skip to content

Commit 65c99d5

Browse files
AzgaarCopilot
andcommitted
refactor: add coastline settings editor and update hotkeys
Co-authored-by: Copilot <copilot@github.com>
1 parent d092da0 commit 65c99d5

7 files changed

Lines changed: 124 additions & 133 deletions

File tree

public/modules/ui/editors.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -544,8 +544,8 @@ function changePickerSpace() {
544544
space === "hex"
545545
? d3.rgb(this.value)
546546
: space === "rgb"
547-
? d3.rgb(i[0], i[1], i[2])
548-
: d3.hsl(i[0], i[1] / 100, i[2] / 100);
547+
? d3.rgb(i[0], i[1], i[2])
548+
: d3.hsl(i[0], i[1] / 100, i[2] / 100);
549549

550550
const hsl = d3.hsl(fill);
551551
if (isNaN(hsl.l)) {
@@ -1006,3 +1006,9 @@ async function editReligions() {
10061006
const Editor = await import("../dynamic/editors/religions-editor.js?v=1.118.0");
10071007
Editor.open();
10081008
}
1009+
1010+
async function editCoastlineSettings() {
1011+
if (customization) return;
1012+
const Editor = await import("../dynamic/editors/coastline-editor.js?v=1.118.0");
1013+
Editor.open();
1014+
}

public/modules/ui/hotkeys.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ function handleKeyup(event) {
4141
else if ((shift || altShift) && code === "KeyS") editStates();
4242
else if ((shift || altShift) && code === "KeyP") editProvinces();
4343
else if ((shift || altShift) && code === "KeyD") editDiplomacy();
44+
else if ((shift || altShift) && code === "KeyL") editCoastlineSettings();
4445
else if ((shift || altShift) && code === "KeyC") editCultures();
4546
else if ((shift || altShift) && code === "KeyN") editNamesbase();
4647
else if ((shift || altShift) && code === "KeyZ") editZones();

public/modules/ui/tools.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ toolsContent.addEventListener("click", function (event) {
1313
else if (button === "editStatesButton") editStates();
1414
else if (button === "editProvincesButton") editProvinces();
1515
else if (button === "editDiplomacyButton") editDiplomacy();
16+
else if (button === "editCoastlineSettings") editCoastlineSettings();
1617
else if (button === "editCulturesButton") editCultures();
1718
else if (button === "editReligions") editReligions();
1819
else if (button === "editEmblemButton") openEmblemEditor();

src/controllers/coastline-editor.ts

Lines changed: 105 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,9 @@ import {
55
coastSettings,
66
fractalize,
77
makeRoughnessProfile,
8-
PROFILE_SIZE,
8+
PROFILE_SIZE
99
} from "../renderers/coastline-fractal";
10-
11-
declare global {
12-
var showCoastlineSettings: () => void;
13-
}
14-
15-
const PREVIEW_SEED = "preview_coastline_42";
10+
import { byId } from "../utils";
1611

1712
interface SliderDef {
1813
id: string;
@@ -32,7 +27,7 @@ const SLIDER_DEFS: SliderDef[] = [
3227
min: 1,
3328
max: 5,
3429
step: 1,
35-
key: "maxDepth",
30+
key: "maxDepth"
3631
},
3732
{
3833
id: "coastBaseAmplitude",
@@ -41,7 +36,7 @@ const SLIDER_DEFS: SliderDef[] = [
4136
min: 0.2,
4237
max: 4,
4338
step: 0.1,
44-
key: "baseAmplitude",
39+
key: "baseAmplitude"
4540
},
4641
{
4742
id: "coastAmplitudeDecay",
@@ -50,7 +45,7 @@ const SLIDER_DEFS: SliderDef[] = [
5045
min: 0.01,
5146
max: 0.99,
5247
step: 0.01,
53-
key: "amplitudeDecay",
48+
key: "amplitudeDecay"
5449
},
5550
{
5651
id: "coastMinEdge",
@@ -59,7 +54,7 @@ const SLIDER_DEFS: SliderDef[] = [
5954
min: 0.1,
6055
max: 10,
6156
step: 0.1,
62-
key: "minEdge",
57+
key: "minEdge"
6358
},
6459
{
6560
id: "coastSmoothThreshold",
@@ -68,7 +63,7 @@ const SLIDER_DEFS: SliderDef[] = [
6863
min: 0.01,
6964
max: 0.5,
7065
step: 0.01,
71-
key: "smoothThreshold",
66+
key: "smoothThreshold"
7267
},
7368
{
7469
id: "coastRoughnessContrast",
@@ -77,10 +72,101 @@ const SLIDER_DEFS: SliderDef[] = [
7772
min: 0.5,
7873
max: 10,
7974
step: 0.1,
80-
key: "roughnessContrast",
81-
},
75+
key: "roughnessContrast"
76+
}
8277
];
8378

79+
const PREVIEW_SEED = "preview_coastline_42";
80+
81+
export function open(): void {
82+
if (!byId("coastlineSettingsDialog")) {
83+
document.body.insertAdjacentHTML("beforeend", buildDialogHTML());
84+
}
85+
86+
for (const {id, key} of SLIDER_DEFS) {
87+
const slider = byId(id) as HTMLInputElement | null;
88+
const output = byId(`${id}Out`) as HTMLElement | null;
89+
const resetBtn = byId(`${id}Reset`) as HTMLElement | null;
90+
91+
if (!slider || !output || !resetBtn) continue;
92+
93+
const defaultVal = coastSettings[key] as number;
94+
95+
slider.on("input", () => {
96+
const value = slider.valueAsNumber;
97+
coastSettings[key] = value;
98+
output.textContent = String(value);
99+
updatePreviews();
100+
drawFeatures();
101+
});
102+
103+
resetBtn.on("click", () => {
104+
(coastSettings[key] as number) = defaultVal;
105+
slider.value = String(defaultVal);
106+
output.textContent = String(defaultVal);
107+
updatePreviews();
108+
drawFeatures();
109+
});
110+
}
111+
112+
updatePreviews();
113+
closeDialogs("#culturesEditor, .stable");
114+
115+
$("#coastlineSettingsDialog").dialog({
116+
title: "Coastline Advanced Settings",
117+
resizable: false,
118+
width: "auto",
119+
position: {my: "center", at: "center", of: "svg"}
120+
});
121+
}
122+
123+
function buildDialogHTML(): string {
124+
const rows = SLIDER_DEFS.map(({id, label, tip, min, max, step, key}) => {
125+
const value = coastSettings[key];
126+
return /* html */ `
127+
<tr data-tip="${tip}">
128+
<td style="padding:4px 8px;white-space:nowrap">${label}</td>
129+
<td style="padding:4px 4px">
130+
<input id="${id}" type="range" min="${min}" max="${max}" step="${step}" value="${value}"
131+
style="width:160px;vertical-align:middle"/>
132+
</td>
133+
<td style="padding:4px 6px;min-width:2.8em;text-align:right">
134+
<span id="${id}Out" style="font-family:monospace;font-size:.85em">${value}</span>
135+
</td>
136+
<td style="padding:4px 4px">
137+
<button id="${id}Reset" title="Reset to default"
138+
style="font-size:.75em;padding:1px 5px;cursor:pointer">↺</button>
139+
</td>
140+
</tr>`;
141+
}).join("");
142+
143+
return /* html */ `
144+
<div id="coastlineSettingsDialog" style="display:none">
145+
<table style="border-collapse:collapse;width:100%">
146+
<tbody>${rows}</tbody>
147+
</table>
148+
<div style="display:flex;gap:6px;margin-top:10px;align-items:flex-start">
149+
<div style="flex:1;min-width:0">
150+
<div style="font-size:.72em;color:#999;margin-bottom:3px">Roughness profile</div>
151+
<canvas id="coastRoughnessGraph" width="266" height="100"
152+
style="border:1px solid #ccc;border-radius:2px;display:block"></canvas>
153+
</div>
154+
<div>
155+
<div style="font-size:.72em;color:#999;margin-bottom:3px">Shape preview</div>
156+
<canvas id="coastShapePreview" width="100" height="100"
157+
style="border:1px solid #ccc;border-radius:2px;display:block"></canvas>
158+
</div>
159+
</div>
160+
</div>`;
161+
}
162+
163+
function updatePreviews(): void {
164+
const graph = byId("coastRoughnessGraph");
165+
const shape = byId("coastShapePreview");
166+
if (graph) drawRoughnessGraph(graph as HTMLCanvasElement);
167+
if (shape) drawShapePreview(shape as HTMLCanvasElement);
168+
}
169+
84170
function drawRoughnessGraph(canvas: HTMLCanvasElement): void {
85171
const W = canvas.width;
86172
const H = canvas.height;
@@ -128,11 +214,7 @@ function drawRoughnessGraph(canvas: HTMLCanvasElement): void {
128214
};
129215

130216
// Helper: stroke curve clipped to a horizontal band
131-
const strokeBand = (
132-
clipTop: number,
133-
clipBot: number,
134-
color: string,
135-
): void => {
217+
const strokeBand = (clipTop: number, clipBot: number, color: string): void => {
136218
const h = clipBot - clipTop;
137219
if (h <= 0) return;
138220
ctx.save();
@@ -196,35 +278,21 @@ function drawShapePreview(canvas: HTMLCanvasElement): void {
196278
[cx, cy - r], // top
197279
[cx + r, cy], // right
198280
[cx, cy + r], // bottom
199-
[cx - r, cy], // left
281+
[cx - r, cy] // left
200282
];
201283

202284
const shape = fractalize(basePts, Alea(PREVIEW_SEED), coastSettings);
203285
const path = new Path2D(`${buildCoastlinePath(shape)}Z`);
204286

205287
// Ocean background — radial gradient, lighter at centre
206-
const bgGrad = ctx.createRadialGradient(
207-
cx,
208-
cy,
209-
0,
210-
cx,
211-
cy,
212-
Math.max(W, H) * 0.85,
213-
);
288+
const bgGrad = ctx.createRadialGradient(cx, cy, 0, cx, cy, Math.max(W, H) * 0.85);
214289
bgGrad.addColorStop(0, "#cce5f5");
215290
bgGrad.addColorStop(1, "#6aa4cb");
216291
ctx.fillStyle = bgGrad;
217292
ctx.fillRect(0, 0, W, H);
218293

219294
// Land fill with drop shadow
220-
const landGrad = ctx.createRadialGradient(
221-
cx - r * 0.1,
222-
cy - r * 0.1,
223-
r * 0.05,
224-
cx,
225-
cy,
226-
r * 1.1,
227-
);
295+
const landGrad = ctx.createRadialGradient(cx - r * 0.1, cy - r * 0.1, r * 0.05, cx, cy, r * 1.1);
228296
landGrad.addColorStop(0, "#d8c87a");
229297
landGrad.addColorStop(0.5, "#9cbc60");
230298
landGrad.addColorStop(1, "#5c8e40");
@@ -244,7 +312,7 @@ function drawShapePreview(canvas: HTMLCanvasElement): void {
244312
ctx.stroke(path);
245313

246314
// Original polygon skeleton — shows the raw 4-vertex input before fractalization
247-
const origPts = shape.origIndices.map((i) => shape.points[i]);
315+
const origPts = shape.origIndices.map(i => shape.points[i]);
248316
ctx.beginPath();
249317
for (let j = 0; j < origPts.length; j++) {
250318
const [x, y] = origPts[j];
@@ -269,96 +337,4 @@ function drawShapePreview(canvas: HTMLCanvasElement): void {
269337
}
270338
}
271339

272-
function updatePreviews(): void {
273-
const graph = document.getElementById("coastRoughnessGraph");
274-
const shape = document.getElementById("coastShapePreview");
275-
if (graph) drawRoughnessGraph(graph as HTMLCanvasElement);
276-
if (shape) drawShapePreview(shape as HTMLCanvasElement);
277-
}
278-
279-
function buildDialogHTML(): string {
280-
const rows = SLIDER_DEFS.map(({ id, label, tip, min, max, step, key }) => {
281-
const val = coastSettings[key] as number;
282-
return /* html */ `
283-
<tr data-tip="${tip}">
284-
<td style="padding:4px 8px;white-space:nowrap">${label}</td>
285-
<td style="padding:4px 4px">
286-
<input id="${id}" type="range" min="${min}" max="${max}" step="${step}" value="${val}"
287-
style="width:160px;vertical-align:middle"/>
288-
</td>
289-
<td style="padding:4px 6px;min-width:2.8em;text-align:right">
290-
<span id="${id}Out" style="font-family:monospace;font-size:.85em">${val}</span>
291-
</td>
292-
<td style="padding:4px 4px">
293-
<button id="${id}Reset" title="Reset to default"
294-
style="font-size:.75em;padding:1px 5px;cursor:pointer">↺</button>
295-
</td>
296-
</tr>`;
297-
}).join("");
298-
299-
return /* html */ `
300-
<div id="coastlineSettingsDialog" style="display:none">
301-
<table style="border-collapse:collapse;width:100%">
302-
<tbody>${rows}</tbody>
303-
</table>
304-
<div style="display:flex;gap:6px;margin-top:10px;align-items:flex-start">
305-
<div style="flex:1;min-width:0">
306-
<div style="font-size:.72em;color:#999;margin-bottom:3px">Roughness profile</div>
307-
<canvas id="coastRoughnessGraph" width="266" height="100"
308-
style="border:1px solid #ccc;border-radius:2px;display:block"></canvas>
309-
</div>
310-
<div>
311-
<div style="font-size:.72em;color:#999;margin-bottom:3px">Shape preview</div>
312-
<canvas id="coastShapePreview" width="100" height="100"
313-
style="border:1px solid #ccc;border-radius:2px;display:block"></canvas>
314-
</div>
315-
</div>
316-
</div>`;
317-
}
318-
319-
function setupCoastlineEditor(): void {
320-
if (!document.getElementById("coastlineSettingsDialog")) {
321-
document.body.insertAdjacentHTML("beforeend", buildDialogHTML());
322-
}
323-
324-
for (const { id, key } of SLIDER_DEFS) {
325-
const slider = document.getElementById(id) as HTMLInputElement | null;
326-
const output = document.getElementById(`${id}Out`) as HTMLElement | null;
327-
const resetBtn = document.getElementById(
328-
`${id}Reset`,
329-
) as HTMLElement | null;
330-
331-
if (!slider || !output || !resetBtn) continue;
332-
333-
const defaultVal = coastSettings[key] as number;
334-
335-
slider.addEventListener("input", () => {
336-
const val = parseFloat(slider.value);
337-
(coastSettings[key] as number) = val;
338-
output.textContent = String(val);
339-
updatePreviews();
340-
if (typeof drawFeatures === "function") drawFeatures();
341-
});
342-
343-
resetBtn.addEventListener("click", () => {
344-
(coastSettings[key] as number) = defaultVal;
345-
slider.value = String(defaultVal);
346-
output.textContent = String(defaultVal);
347-
updatePreviews();
348-
if (typeof drawFeatures === "function") drawFeatures();
349-
});
350-
}
351-
352-
updatePreviews();
353-
354-
window.showCoastlineSettings = () => {
355-
$("#coastlineSettingsDialog").dialog({
356-
title: "Coastline Advanced Settings",
357-
resizable: false,
358-
width: "auto",
359-
position: { my: "center", at: "center", of: "svg" },
360-
});
361-
};
362-
}
363340

364-
setupCoastlineEditor();

src/index.html

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2113,6 +2113,13 @@
21132113
<button id="overviewBurgsButton" data-tip="Click to open Burgs Overview" data-shortcut="Shift + T">
21142114
Burgs
21152115
</button>
2116+
<button
2117+
id="editCoastlineSettings"
2118+
data-tip="Click to open Coastline Settings Editor"
2119+
data-shortcut="Shift + L"
2120+
>
2121+
Coastlines
2122+
</button>
21162123
<button id="editCulturesButton" data-tip="Click to open Cultures Editor" data-shortcut="Shift + C">
21172124
Cultures
21182125
</button>
@@ -8585,7 +8592,6 @@
85858592
<script defer src="modules/ui/route-group-editor.js?v=1.103.8"></script>
85868593
<script defer src="modules/ui/ice-editor.js?v=1.111.0"></script>
85878594
<script defer src="modules/ui/lakes-editor.js?v=1.106.0"></script>
8588-
<script defer src="modules/ui/coastline-editor.js?v=1.99.00"></script>
85898595
<script defer src="modules/ui/labels-editor.js?v=1.113.2"></script>
85908596
<script defer src="modules/ui/rivers-editor.js?v=1.106.0"></script>
85918597
<script defer src="modules/ui/rivers-creator.js?v=1.106.0"></script>

src/renderers/coastline-fractal.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ export function fractalize(
175175
for (let i = 0; i < n; i++) {
176176
origIndices.push(resultPts.length);
177177
resultPts.push(points[i]);
178-
if (isOnBorder(points[i])) continue; // Skip fractal displacement for points on map border
178+
if (isOnBorder(points[i]) && isOnBorder(points[(i + 1) % n])) continue; // Skip edges running along the map border
179179

180180
const [x0, y0] = points[i];
181181
const [x1, y1] = points[(i + 1) % n];

src/types/global.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,5 @@ declare global {
9696
var reGraph: () => void;
9797
var createDefaultRuler: () => void;
9898
var showStatistics: () => void;
99+
var closeDialogs: (except?: string) => void;
99100
}

0 commit comments

Comments
 (0)