concept-collection / turing-surface
440 lines · 19.5 KBCodeBlameHistory
0af3386Reaction-diffusion on spherical-harmonic surfacesJeremy Magland 1<!doctype html>
2<html lang="en">
3 <head>
4 <meta charset="utf-8" />
5 <meta name="viewport" content="width=device-width, initial-scale=1" />
6 <link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><circle cx=%2250%22 cy=%2250%22 r=%2245%22 fill=%22%232a7f62%22/><circle cx=%2235%22 cy=%2238%22 r=%2211%22 fill=%22%23f5d547%22/><circle cx=%2265%22 cy=%2258%22 r=%229%22 fill=%22%23f5d547%22/><circle cx=%2248%22 cy=%2274%22 r=%227%22 fill=%22%23f5d547%22/><circle cx=%2268%22 cy=%2230%22 r=%226%22 fill=%22%23f5d547%22/></svg>" />
7 <title>turing-surface — reaction-diffusion on closed surfaces, live in the browser</title>
8 <style>
9 :root {
10 --bg: #ffffff;
11 --ink: #1f2328;
12 --ink-2: #57606a;
13 --line: #d0d7de;
14 --accent: #0969da;
15 --sphere-bg: #f4f6f8;
16 --tok-com: #6e7781;
17 --tok-str: #0a3069;
18 --tok-num: #0550ae;
19 --tok-kw: #cf222e;
20 --tok-ext: #8250df;
21 color-scheme: light dark;
22 }
23 @media (prefers-color-scheme: dark) {
24 :root {
25 --bg: #14171a;
26 --ink: #e6e9ec;
27 --ink-2: #9aa4af;
28 --line: #333b44;
29 --accent: #58a6ff;
30 --sphere-bg: #14161c;
31 --tok-com: #8b949e;
32 --tok-str: #a5d6ff;
33 --tok-num: #79c0ff;
34 --tok-kw: #ff7b72;
35 --tok-ext: #d2a8ff;
36 }
37 }
38 body {
39 margin: 0;
40 background: var(--bg);
41 color: var(--ink);
42 font: 15px/1.5 system-ui, -apple-system, sans-serif;
43 }
44 main { max-width: 1100px; margin: 0 auto; padding: 20px 16px 48px; }
45 h1 { font-size: 20px; margin: 0 0 2px; }
46 .sub { color: var(--ink-2); margin: 0 0 12px; font-size: 13px; }
47 .sub a { color: var(--accent); }
48 .controls {
49 display: flex; flex-wrap: wrap; gap: 8px 14px; align-items: center;
50 padding: 6px 0;
51 }
52 /* display:flex above would otherwise override the UA's [hidden] rule */
53 .controls[hidden] { display: none; }
55 display: flex; flex-wrap: wrap; gap: 8px;
56 padding: 4px 0 10px; margin-bottom: 4px;
57 border-bottom: 1px solid var(--line);
58 }
59 .modes .chip { font-size: 13px; padding: 4px 12px; }
2bb4f78Add short descriptions for each modeOwen Melia 60 .mode-desc {
61 color: var(--ink); margin: 0 0 12px; font-size: 13.5px;
62 padding: 10px 14px; border-radius: 6px;
63 border-left: 3px solid var(--accent);
64 background: color-mix(in srgb, var(--accent) 10%, var(--bg));
65 }
66 .mode-desc:empty { display: none; }
0af3386Reaction-diffusion on spherical-harmonic surfacesJeremy Magland 67 .controls label { color: var(--ink-2); font-size: 13px; white-space: nowrap; }
68 select, input[type="number"], button {
69 font: inherit; font-size: 13px;
70 color: var(--ink); background: var(--bg);
71 border: 1px solid var(--line); border-radius: 6px;
72 padding: 4px 8px;
73 }
74 input[type="number"] { width: 6em; }
75 input[type="range"] { width: 8em; vertical-align: middle; accent-color: var(--accent); }
76 button { cursor: pointer; }
77 #geomnote { margin-top: 4px; }
78 button:hover { border-color: var(--accent); }
79 button.primary { border-color: var(--accent); color: var(--accent); font-weight: 600; min-width: 5.5em; }
80 #panels {
81 display: flex; flex-wrap: wrap; gap: 14px; margin-top: 12px;
82 }
83 .panel {
84 flex: 1 1 320px; min-width: 280px;
85 border: 1px solid var(--line); border-radius: 8px; overflow: hidden;
86 display: flex;
87 }
88 .sphere-box { flex: 1; aspect-ratio: 1 / 1; max-height: 70vh; position: relative; }
89 .species-tag {
90 position: absolute; top: 8px; left: 10px; z-index: 2;
91 font-size: 15px; font-weight: 600; color: #fff;
92 background: rgba(0, 0, 0, 0.45);
93 padding: 1px 10px; border-radius: 12px;
94 pointer-events: none;
95 }
96 .colorbar {
97 display: flex; flex-direction: column; align-items: center; justify-content: center;
98 gap: 4px; padding: 8px 4px; background: var(--sphere-bg);
99 width: 52px; flex: none; box-sizing: border-box;
100 }
101 .colorbar canvas { border: 1px solid var(--line); border-radius: 2px; }
102 .colorbar-label { font-size: 11px; color: var(--ink-2); font-variant-numeric: tabular-nums; }
103 .stats { margin-top: 10px; font-size: 13px; color: var(--ink-2); font-variant-numeric: tabular-nums; }
104 .stats b { color: var(--ink); font-weight: 600; }
105 .cli {
106 margin-top: 10px; border: 1px solid var(--line); border-radius: 8px;
107 overflow: hidden;
108 }
109 .cli-head {
110 display: flex; gap: 10px; align-items: center; justify-content: space-between;
111 padding: 6px 10px; font-size: 12px; color: var(--ink-2);
112 background: var(--sphere-bg); border-bottom: 1px solid var(--line);
113 }
114 .cli-head button { padding: 2px 10px; font-size: 12px; }
115 #cmd {
116 display: block; padding: 8px 10px; white-space: pre-wrap;
117 font: 12px/1.6 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
118 color: var(--ink); user-select: all;
119 }
9b71d5bexport standalone matlabJeremy Magland 120 details.cli > summary { cursor: pointer; }
121 details.cli > summary::before { content: '▸'; font-size: 10px; color: var(--ink-2); }
122 details.cli[open] > summary::before { content: '▾'; }
123 #matlabscript {
124 margin: 0; padding: 8px 10px; max-height: 30em; overflow: auto;
125 font: 12px/1.5 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
126 color: var(--ink); white-space: pre; user-select: text;
127 }
0af3386Reaction-diffusion on spherical-harmonic surfacesJeremy Magland 128 #blurb { margin-top: 4px; font-size: 13px; color: var(--ink-2); }
129 #err { color: #b35900; white-space: pre-wrap; font-size: 13px; }
130 .editor {
131 margin-top: 12px; border: 1px solid var(--line); border-radius: 8px;
132 overflow: hidden;
133 }
134 .editor-head {
135 display: flex; gap: 10px; align-items: center; justify-content: space-between;
136 padding: 6px 10px; font-size: 12px; color: var(--ink-2);
137 background: var(--sphere-bg); border-bottom: 1px solid var(--line);
138 }
139 .editor-head button { padding: 2px 10px; font-size: 12px; }
9389d73WIP: First draft at new interface with multiple comparison modesOwen Melia 140 /* Source and compiled-op list side by side. The fixed height lives on
141 the row itself, not on either child: a child's own `height` would
142 only be a *hint* stretch fills when unset, and `#compiled` sets its
143 own smaller font, so the same em value would resolve to a shorter
144 box than .editor-code's. Sizing the row instead makes both children
145 stretch to one shared pixel height regardless of either's font. */
146 .editor-body { display: flex; align-items: stretch; height: 34em; }
147 .editor-code { position: relative; flex: 1 1 62%; min-width: 0; }
0af3386Reaction-diffusion on spherical-harmonic surfacesJeremy Magland 148 /* The overlay and the textarea must agree on every metric that affects
149 where a character lands. Keep these two rules together. */
150 .editor-code > pre,
151 .editor-code > textarea {
152 margin: 0; padding: 10px 12px; border: 0;
153 box-sizing: border-box; width: 100%; height: 100%;
154 font: 12.5px/1.6 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
155 tab-size: 2;
156 white-space: pre; overflow-wrap: normal;
157 }
158 #highlight {
159 position: absolute; inset: 0; overflow: hidden;
160 pointer-events: none; background: var(--bg); color: var(--ink);
161 }
162 #source {
163 position: relative; z-index: 1; display: block;
164 resize: none; overflow: auto;
165 background: transparent; color: transparent; caret-color: var(--ink);
166 }
167 #source:focus { outline: none; }
168 /* Transparent text means the selection must be see-through, or selected
169 code would be invisible. */
170 #source::selection { background: color-mix(in srgb, var(--accent) 28%, transparent); }
171 #compiled {
172 flex: 1 1 38%; min-width: 0; margin: 0; padding: 10px 12px; overflow: auto;
173 border-left: 1px solid var(--line); background: var(--sphere-bg);
174 font: 12px/1.6 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
175 color: var(--ink-2); white-space: pre;
176 }
177 @media (max-width: 860px) {
9389d73WIP: First draft at new interface with multiple comparison modesOwen Melia 178 /* Stacked, so each panel goes back to managing its own height rather
179 than sharing the row's fixed one. */
180 .editor-body { flex-direction: column; height: auto; }
0af3386Reaction-diffusion on spherical-harmonic surfacesJeremy Magland 181 .editor-code { flex: none; height: 26em; }
182 #compiled { border-left: 0; border-top: 1px solid var(--line); max-height: 12em; }
183 }
f467ffaCompare several solver settings side by side, on one clockJeremy Magland 184 /* ---- compare mode ------------------------------------------------ */
185 /* The bar's own layout: three chip rows stacked, then the reference
186 picker and the button beside them. */
187 .cmp-axes { display: flex; flex-direction: column; gap: 4px; }
188 .cmp-axis { display: flex; align-items: center; gap: 8px; }
189 .cmp-axis > span:first-child {
190 color: var(--ink-2); font-size: 13px; width: 5.5em; text-align: right;
191 }
192 .chips { display: flex; flex-wrap: wrap; gap: 4px; }
193 .chip {
194 font: inherit; font-size: 12px; padding: 2px 8px;
195 border: 1px solid var(--line); border-radius: 999px;
196 background: var(--bg); color: var(--ink-2); cursor: pointer;
197 }
198 .chip:hover { border-color: var(--accent); }
199 .chip[aria-pressed="true"] {
200 border-color: var(--accent); color: var(--accent);
201 background: color-mix(in srgb, var(--accent) 12%, transparent);
202 font-weight: 600;
203 }
204 /* The panel area becomes a stack of labelled rows. Overrides the flex
205 wrap the single-run view uses. */
206 #panels.compare { flex-direction: column; gap: 8px; }
207 .cmp-row { display: flex; align-items: stretch; gap: 8px; }
208 .cmp-rowlabel {
209 flex: none; width: 13em; padding: 6px 8px;
210 border-left: 4px solid var(--c, var(--line));
211 font-size: 12px; color: var(--ink-2);
212 display: flex; flex-direction: column; justify-content: center; gap: 3px;
213 }
214 .cmp-rowname { color: var(--ink); font-weight: 600; }
215 .cmp-rowstat { font-variant-numeric: tabular-nums; line-height: 1.35; }
216 .cmp-diverged { color: var(--warn-edge); }
217 /* The header row's label cell is a spacer, not a variant — no swatch. */
218 .cmp-head .cmp-rowlabel { border-left-color: transparent; padding: 0 8px; }
219 .cmp-cols { flex: 1; display: flex; gap: 8px; min-width: 0; }
220 .cmp-box {
221 flex: 1 1 0; min-width: 0;
222 border: 1px solid var(--line); border-radius: 8px; overflow: hidden;
223 max-height: 42vh;
224 }
225 .cmp-head { align-items: flex-end; }
226 .cmp-colhead {
227 flex: 1 1 0; min-width: 0;
228 display: flex; align-items: center; gap: 8px;
229 font-size: 12px; color: var(--ink-2);
230 }
231 .cmp-rangebar {
232 flex: 1 1 auto; min-width: 0; height: 8px;
233 border: 1px solid var(--line); border-radius: 2px;
234 }
235 .cmp-rangelab { font-variant-numeric: tabular-nums; white-space: nowrap; }
236 @media (max-width: 860px) {
237 .cmp-row { flex-direction: column; }
238 .cmp-rowlabel { width: auto; flex-direction: row; gap: 10px; }
239 .cmp-head { display: none; }
240 }
0af3386Reaction-diffusion on spherical-harmonic surfacesJeremy Magland 241 .tok-com { color: var(--tok-com); }
242 .tok-str { color: var(--tok-str); }
243 .tok-num { color: var(--tok-num); }
244 .tok-kw { color: var(--tok-kw); font-weight: 600; }
245 .tok-ext { color: var(--tok-ext); }
246 </style>
247 </head>
248 <body>
249 <main>
250 <h1>turing-surface</h1>
251 <p class="sub">
62e6cc4Simplify UI text and built-in .m script commentsJeremy Magland 252 Reaction-diffusion on closed surfaces, solved live with spherical
253 harmonics on WebGPU via
0af3386Reaction-diffusion on spherical-harmonic surfacesJeremy Magland 254 <a href="https://github.com/concept-collection/shtns-webgpu">shtns-webgpu</a>.
f132fe4Drop the work-in-progress bannerDan Fortunato 255 Both the solver and the shape are the MATLAB below, run in your
62e6cc4Simplify UI text and built-in .m script commentsJeremy Magland 256 browser by <a href="https://numbl.org">numbl</a>. Edit either and watch
257 it change. Drag to rotate.
9389d73WIP: First draft at new interface with multiple comparison modesOwen Melia 259 <div class="modes" id="modebar">
260 <button type="button" class="chip" id="mode-simulate" aria-pressed="true">Simulate</button>
261 <button type="button" class="chip" id="mode-effort"
262 title="Run several solver settings side by side on one clock">Compare computational effort</button>
c8e230aFixing some bugs in the UIOwen Melia 263 <button type="button" class="chip" id="mode-vs-sphere" disabled title="Coming soon">Compare against sphere (Coming soon)</button>
9389d73WIP: First draft at new interface with multiple comparison modesOwen Melia 264 <button type="button" class="chip" id="mode-vs-upload"
c8e230aFixing some bugs in the UIOwen Melia 265 title="Check this solver against a saved reference run">
9389d73WIP: First draft at new interface with multiple comparison modesOwen Melia 266 Compare against uploaded data</button>
267 <input type="file" id="cmp-file" accept=".h5" hidden>
268 </div>
2bb4f78Add short descriptions for each modeOwen Melia 269 <p class="mode-desc" id="mode-desc"></p>
a158c73Update ids in index.html for easier refactorOwen Melia 270 <div class="controls ctrl-group" data-group="surface">
c8e230aFixing some bugs in the UIOwen Melia 271 <label>reaction-diffusion model
0af3386Reaction-diffusion on spherical-harmonic surfacesJeremy Magland 272 <select id="model"></select>
273 </label>
f132fe4Drop the work-in-progress bannerDan Fortunato 274 <label title="The surface the pattern is solved on. Swapping it does not recompile the solver or restart the run.">geometry
0af3386Reaction-diffusion on spherical-harmonic surfacesJeremy Magland 275 <select id="geometry"></select>
276 </label>
278 <div class="ctrl-group" data-group="surface-params">
279 <div class="controls" id="params"></div>
280 <div class="controls" id="geomparams"></div>
281 </div>
282 <div class="controls" id="comparebar" hidden>
283 <div class="cmp-axes">
284 <div class="cmp-axis">
285 <span title="Iterations of the implicit diffusion solve">solve iters</span>
286 <span id="cmp-niter" class="chips"></span>
287 </div>
288 <div class="cmp-axis">
289 <span>lmax</span>
290 <span id="cmp-lmax" class="chips"></span>
291 </div>
292 <div class="cmp-axis">
293 <span title="Timestep, as a divisor of the model's dt. Divisors keep every variant on the same clock exactly.">dt</span>
294 <span id="cmp-dt" class="chips"></span>
295 </div>
296 </div>
297 <label title="The run everything else is measured against">reference
298 <select id="cmp-ref"></select>
a158c73Update ids in index.html for easier refactorOwen Melia 300 <span id="cmp-fileinfo" class="stats" hidden></span>
301 <button id="cmp-fileclear" hidden
302 title="Drop the reference file and compare the variants against each other again">×</button>
c8e230aFixing some bugs in the UIOwen Melia 303 <button id="cmp-start" class="primary">Compile comparison</button>
a158c73Update ids in index.html for easier refactorOwen Melia 304 <span id="cmp-count" class="stats"></span>
305 </div>
306 <div class="controls ctrl-group" data-group="solver">
62e6cc4Simplify UI text and built-in .m script commentsJeremy Magland 307 <label title="Iterations of the implicit diffusion solve. Changing it recompiles.">solve iters
0af3386Reaction-diffusion on spherical-harmonic surfacesJeremy Magland 308 <select id="niter">
309 <option value="0">0</option>
f467ffaCompare several solver settings side by side, on one clockJeremy Magland 310 <option value="1">1</option>
0af3386Reaction-diffusion on spherical-harmonic surfacesJeremy Magland 311 <option value="2">2</option>
312 <option value="4">4</option>
f467ffaCompare several solver settings side by side, on one clockJeremy Magland 313 <option value="8" selected>8</option>
067895eadditional options for niterJeremy Magland 314 <option value="16">16</option>
315 <option value="32">32</option>
316 <option value="64">64</option>
317 <option value="128">128</option>
319 </label>
320 <label>lmax
321 <select id="lmax">
322 <option value="31">31</option>
323 <option value="63" selected>63</option>
324 <option value="127">127</option>
325 <option value="255">255</option>
326 </select>
327 </label>
329 <div class="controls ctrl-group" data-group="display">
62e6cc4Simplify UI text and built-in .m script commentsJeremy Magland 330 <label title="Render on a finer grid. Display only.">display oversampling
0af3386Reaction-diffusion on spherical-harmonic surfacesJeremy Magland 331 <select id="oversample">
332 <option value="auto" selected>auto</option>
333 <option value="1">off</option>
334 <option value="2"></option>
335 <option value="4"></option>
336 <option value="8"></option>
337 </select>
338 </label>
339 <label>colormap
340 <select id="colormap"></select>
341 </label>
a158c73Update ids in index.html for easier refactorOwen Melia 342 <label title="Blend between the sphere (0) and the surface (1). Display only.">morph
343 <input type="range" id="morph" min="0" max="1" step="0.01" value="1" />
344 </label>
345 <button id="resetview">Reset view</button>
346 </div>
347 <div class="controls ctrl-group" data-group="playback">
0af3386Reaction-diffusion on spherical-harmonic surfacesJeremy Magland 348 <button id="runpause" class="primary">Run</button>
ca37955Adding a way to reset simulation from same random IC.Owen Melia 349 <button id="restart" title="Rewind to the initial condition this run started from, without drawing a new one">Restart</button>
351 <div class="controls ctrl-group" data-group="benchmark">
0af3386Reaction-diffusion on spherical-harmonic surfacesJeremy Magland 352 <button id="benchmark">Benchmark</button>
354 <div class="controls ctrl-group" data-group="seed">
ca37955Adding a way to reset simulation from same random IC.Owen Melia 355 <label title="Wavelength of the smooth random field generating the initial condition">Initial condition wavelength λ
0ae15cfSeed runs from smooth random fields, and add the blob geometryDan Fortunato 356 <input id="lam3" type="number" min="0" step="0.05" value="0.5">
357 </label>
0af3386Reaction-diffusion on spherical-harmonic surfacesJeremy Magland 358 <button id="reseed">Re-seed</button>
a158c73Update ids in index.html for easier refactorOwen Melia 360 <div class="controls ctrl-group" data-group="movie">
361 <button id="movietoggle" title="Export the run as an MP4 movie">Export movie</button>
362 <div class="controls" id="moviebar" hidden>
363 <label title="Simulation-time units per second of video">movie speed
364 <select id="moviespeed">
365 <option value="0.1">0.1×</option>
366 <option value="0.5">0.5×</option>
367 <option value="1"></option>
368 <option value="3"></option>
369 <option value="5"></option>
370 <option value="10" selected>10×</option>
371 <option value="20">20×</option>
372 </select>
373 </label>
374 <label title="Size of each sphere panel in the video, in pixels">resolution
375 <select id="movieres">
376 <option value="480">480</option>
377 <option value="640">640</option>
378 <option value="768" selected>768</option>
379 <option value="1080">1080</option>
380 <option value="1440">1440</option>
381 </select>
382 </label>
383 <label title="Slowly orbit the camera during the movie">
384 <input type="checkbox" id="movierotate" checked /> auto-rotate
385 </label>
386 <button id="movie" title="Replay the run from t = 0 and download an MP4">Export</button>
389 <p id="geomnote" class="stats"></p>
390 <div id="panels"></div>
391 <p class="stats" id="stats"></p>
392 <p class="stats" id="benchresult"></p>
393 <div class="editor">
394 <div class="editor-head">
395 <span>
396 <select id="editor-file" aria-label="file to edit"></select>
397 <span id="editor-title"></span>
398 </span>
399 <span>
400 <button id="recompile" type="button">Recompile</button>
401 <button id="revert" type="button">Revert</button>
402 </span>
403 </div>
404 <div class="editor-body">
405 <div class="editor-code">
406 <pre id="highlight" aria-hidden="true"></pre>
407 <textarea
408 id="source"
409 spellcheck="false"
410 autocomplete="off"
411 autocapitalize="off"
412 aria-label="model source (MATLAB)"
413 ></textarea>
414 </div>
415 <pre id="compiled"></pre>
416 </div>
417 </div>
418 <div class="cli">
419 <div class="cli-head">
62e6cc4Simplify UI text and built-in .m script commentsJeremy Magland 420 <span>The same run on the desktop</span>
0af3386Reaction-diffusion on spherical-harmonic surfacesJeremy Magland 421 <button id="copycmd" type="button">Copy</button>
422 </div>
423 <code id="cmd"></code>
424 </div>
9b71d5bexport standalone matlabJeremy Magland 425 <details class="cli" id="matlab">
426 <summary class="cli-head">
427 <span>The same run as a standalone MATLAB script</span>
428 <span>
429 <button id="copymatlab" type="button">Copy</button>
430 <button id="downloadmatlab" type="button">Download .m</button>
431 </span>
432 </summary>
433 <pre id="matlabscript"></pre>
434 </details>
0af3386Reaction-diffusion on spherical-harmonic surfacesJeremy Magland 435 <p id="blurb"></p>
436 <p id="err"></p>
437 </main>
438 <script type="module" src="/src/main.ts"></script>
439 </body>
440</html>