@@ -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
1712interface 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+
84170function 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 ( ) ;
0 commit comments