/ concept-collection / turing-surface
Sign in
concept-collection / turing-surface
319 lines · 14.2 KBBlameHistoryRaw
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 --warn-bg: #fff8e5;
22 --warn-line: #e3c37a;
23 --warn-edge: #bf8700;
24 color-scheme: light dark;
25 }
26 @media (prefers-color-scheme: dark) {
27 :root {
28 --bg: #14171a;
29 --ink: #e6e9ec;
30 --ink-2: #9aa4af;
31 --line: #333b44;
32 --accent: #58a6ff;
33 --sphere-bg: #14161c;
34 --tok-com: #8b949e;
35 --tok-str: #a5d6ff;
36 --tok-num: #79c0ff;
37 --tok-kw: #ff7b72;
38 --tok-ext: #d2a8ff;
39 --warn-bg: #2b2410;
40 --warn-line: #6b5518;
41 --warn-edge: #e3b341;
42 }
43 }
44 body {
45 margin: 0;
46 background: var(--bg);
47 color: var(--ink);
48 font: 15px/1.5 system-ui, -apple-system, sans-serif;
49 }
50 main { max-width: 1100px; margin: 0 auto; padding: 20px 16px 48px; }
51 h1 { font-size: 20px; margin: 0 0 2px; }
52 .sub { color: var(--ink-2); margin: 0 0 12px; font-size: 13px; }
53 .sub a { color: var(--accent); }
54 .controls {
55 display: flex; flex-wrap: wrap; gap: 8px 14px; align-items: center;
56 padding: 6px 0;
57 }
58 /* display:flex above would otherwise override the UA's [hidden] rule */
59 .controls[hidden] { display: none; }
60 .controls label { color: var(--ink-2); font-size: 13px; white-space: nowrap; }
61 select, input[type="number"], button {
62 font: inherit; font-size: 13px;
63 color: var(--ink); background: var(--bg);
64 border: 1px solid var(--line); border-radius: 6px;
65 padding: 4px 8px;
66 }
67 input[type="number"] { width: 6em; }
68 input[type="range"] { width: 8em; vertical-align: middle; accent-color: var(--accent); }
69 button { cursor: pointer; }
70 #geomnote { margin-top: 4px; }
71 button:hover { border-color: var(--accent); }
72 button.primary { border-color: var(--accent); color: var(--accent); font-weight: 600; min-width: 5.5em; }
73 #panels {
74 display: flex; flex-wrap: wrap; gap: 14px; margin-top: 12px;
75 }
76 .panel {
77 flex: 1 1 320px; min-width: 280px;
78 border: 1px solid var(--line); border-radius: 8px; overflow: hidden;
79 display: flex;
80 }
81 .sphere-box { flex: 1; aspect-ratio: 1 / 1; max-height: 70vh; position: relative; }
82 .species-tag {
83 position: absolute; top: 8px; left: 10px; z-index: 2;
84 font-size: 15px; font-weight: 600; color: #fff;
85 background: rgba(0, 0, 0, 0.45);
86 padding: 1px 10px; border-radius: 12px;
87 pointer-events: none;
88 }
89 .colorbar {
90 display: flex; flex-direction: column; align-items: center; justify-content: center;
91 gap: 4px; padding: 8px 4px; background: var(--sphere-bg);
92 width: 52px; flex: none; box-sizing: border-box;
93 }
94 .colorbar canvas { border: 1px solid var(--line); border-radius: 2px; }
95 .colorbar-label { font-size: 11px; color: var(--ink-2); font-variant-numeric: tabular-nums; }
96 .stats { margin-top: 10px; font-size: 13px; color: var(--ink-2); font-variant-numeric: tabular-nums; }
97 .stats b { color: var(--ink); font-weight: 600; }
98 .cli {
99 margin-top: 10px; border: 1px solid var(--line); border-radius: 8px;
100 overflow: hidden;
101 }
102 .cli-head {
103 display: flex; gap: 10px; align-items: center; justify-content: space-between;
104 padding: 6px 10px; font-size: 12px; color: var(--ink-2);
105 background: var(--sphere-bg); border-bottom: 1px solid var(--line);
106 }
107 .cli-head button { padding: 2px 10px; font-size: 12px; }
108 #cmd {
109 display: block; padding: 8px 10px; white-space: pre-wrap;
110 font: 12px/1.6 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
111 color: var(--ink); user-select: all;
112 }
113 #blurb { margin-top: 4px; font-size: 13px; color: var(--ink-2); }
114 .warn {
115 margin: 0 0 14px;
116 padding: 10px 14px;
117 border: 1px solid var(--warn-line);
118 border-left: 5px solid var(--warn-edge);
119 border-radius: 6px;
120 background: var(--warn-bg);
121 color: var(--ink);
122 font-size: 13.5px; line-height: 1.5;
123 }
124 .warn b { color: var(--warn-edge); }
125 #err { color: #b35900; white-space: pre-wrap; font-size: 13px; }
126 .editor {
127 margin-top: 12px; border: 1px solid var(--line); border-radius: 8px;
128 overflow: hidden;
129 }
130 .editor-head {
131 display: flex; gap: 10px; align-items: center; justify-content: space-between;
132 padding: 6px 10px; font-size: 12px; color: var(--ink-2);
133 background: var(--sphere-bg); border-bottom: 1px solid var(--line);
134 }
135 .editor-head button { padding: 2px 10px; font-size: 12px; }
136 /* Source and compiled-op list side by side, so the editor gets the
137 height rather than sharing it with the list below. */
138 .editor-body { display: flex; align-items: stretch; }
139 .editor-code { position: relative; flex: 1 1 62%; min-width: 0; height: 34em; }
140 /* The overlay and the textarea must agree on every metric that affects
141 where a character lands. Keep these two rules together. */
142 .editor-code > pre,
143 .editor-code > textarea {
144 margin: 0; padding: 10px 12px; border: 0;
145 box-sizing: border-box; width: 100%; height: 100%;
146 font: 12.5px/1.6 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
147 tab-size: 2;
148 white-space: pre; overflow-wrap: normal;
149 }
150 #highlight {
151 position: absolute; inset: 0; overflow: hidden;
152 pointer-events: none; background: var(--bg); color: var(--ink);
153 }
154 #source {
155 position: relative; z-index: 1; display: block;
156 resize: none; overflow: auto;
157 background: transparent; color: transparent; caret-color: var(--ink);
158 }
159 #source:focus { outline: none; }
160 /* Transparent text means the selection must be see-through, or selected
161 code would be invisible. */
162 #source::selection { background: color-mix(in srgb, var(--accent) 28%, transparent); }
163 #compiled {
164 flex: 1 1 38%; min-width: 0; margin: 0; padding: 10px 12px; overflow: auto;
165 border-left: 1px solid var(--line); background: var(--sphere-bg);
166 font: 12px/1.6 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
167 color: var(--ink-2); white-space: pre;
168 }
169 @media (max-width: 860px) {
170 .editor-body { flex-direction: column; }
171 .editor-code { flex: none; height: 26em; }
172 #compiled { border-left: 0; border-top: 1px solid var(--line); max-height: 12em; }
173 }
174 .tok-com { color: var(--tok-com); }
175 .tok-str { color: var(--tok-str); }
176 .tok-num { color: var(--tok-num); }
177 .tok-kw { color: var(--tok-kw); font-weight: 600; }
178 .tok-ext { color: var(--tok-ext); }
179 </style>
180 </head>
181 <body>
182 <main>
183 <h1>turing-surface</h1>
184 <p class="warn">
185 <b>⚠ Work in progress: the geometry is drawn, not solved on.</b>
186 The Laplace–Beltrami operator in the models is still the <em>round
187 sphere's</em> — the term that would carry the shape is a placeholder
188 that is identically zero. So on anything but the sphere, what you see
189 is the sphere's pattern painted onto that surface, not the pattern that
190 surface would actually grow. The spot size does not follow the
191 curvature, and it will not until that placeholder is filled in.
192 Everything else — the embedding, the mesh, the unrolled implicit solve
193 the correction plugs into — is real and in place.
194 </p>
195 <p class="sub">
196 Reaction-diffusion on closed surfaces, solved live with spherical harmonics:
197 implicit spectral diffusion + explicit reaction (IMEX Euler), transforms on WebGPU via
198 <a href="https://github.com/concept-collection/shtns-webgpu">shtns-webgpu</a>.
199 The surface is an embedding of the sphere given by spherical-harmonic
200 coefficients for x, y and z. Both the solver and the shape are the
201 MATLAB below — <a href="https://numbl.org">numbl</a> parses and lowers
202 them in your browser, and each line becomes a WebGPU compute kernel.
203 Edit either and watch it change. Drag to rotate.
204 </p>
205 <div class="controls">
206 <label>preset
207 <select id="model"></select>
208 </label>
209 <label title="The surface, as a spherical-harmonic embedding. Rendered exactly; NOT yet in the Laplace-Beltrami operator, which is still the round-sphere one.">geometry
210 <select id="geometry"></select>
211 </label>
212 <label title="Blend between the unit sphere (0) and the surface (1). Display only — the solver's grid is the sphere's parametrization either way.">morph
213 <input type="range" id="morph" min="0" max="1" step="0.01" value="1" />
214 </label>
215 <label title="Iterations of the implicit diffusion solve, unrolled into the compiled step. Changing it recompiles. The correction it iterates is zero until the geometry reaches the operator, so today every value gives the same answer.">solve iters
216 <select id="niter">
217 <option value="0">0</option>
218 <option value="1" selected>1</option>
219 <option value="2">2</option>
220 <option value="4">4</option>
221 <option value="8">8</option>
222 </select>
223 </label>
224 <label>lmax
225 <select id="lmax">
226 <option value="31">31</option>
227 <option value="63" selected>63</option>
228 <option value="127">127</option>
229 <option value="255">255</option>
230 </select>
231 </label>
232 <label title="Display only — the solution is evaluated on a finer grid for rendering; the solver and its grid are unchanged. Auto oversamples coarse grids and leaves fine ones alone.">display oversampling
233 <select id="oversample">
234 <option value="auto" selected>auto</option>
235 <option value="1">off</option>
236 <option value="2"></option>
237 <option value="4"></option>
238 <option value="8"></option>
239 </select>
240 </label>
241 <label>colormap
242 <select id="colormap"></select>
243 </label>
244 <button id="runpause" class="primary">Run</button>
245 <button id="benchmark">Benchmark</button>
246 <button id="reseed">Re-seed</button>
247 <button id="resetview">Reset view</button>
248 <button id="movietoggle" title="Export the run as an MP4 movie">Export movie</button>
249 </div>
250 <div class="controls" id="moviebar" hidden>
251 <label title="Playback speed: simulation-time units per second of video — 1× plays one time unit per second">movie speed
252 <select id="moviespeed">
253 <option value="0.1">0.1×</option>
254 <option value="0.5">0.5×</option>
255 <option value="1"></option>
256 <option value="3"></option>
257 <option value="5"></option>
258 <option value="10" selected>10×</option>
259 <option value="20">20×</option>
260 </select>
261 </label>
262 <label title="Rendered size of each sphere panel, in pixels — the video frame is the panels side by side plus the caption. Independent of the window size.">resolution
263 <select id="movieres">
264 <option value="480">480</option>
265 <option value="640">640</option>
266 <option value="768" selected>768</option>
267 <option value="1080">1080</option>
268 <option value="1440">1440</option>
269 </select>
270 </label>
271 <label title="Slowly orbit the camera during the movie — one revolution per 2 minutes of video, starting from the current view (which is restored afterwards)">
272 <input type="checkbox" id="movierotate" checked /> auto-rotate
273 </label>
274 <button id="movie" title="Recompute the run from t = 0 and download it as an MP4 movie">Export</button>
275 </div>
276 <div class="controls" id="params"></div>
277 <div class="controls" id="geomparams"></div>
278 <p id="geomnote" class="stats"></p>
279 <div id="panels"></div>
280 <p class="stats" id="stats"></p>
281 <p class="stats" id="benchresult"></p>
282 <div class="editor">
283 <div class="editor-head">
284 <span>
285 <select id="editor-file" aria-label="file to edit"></select>
286 <span id="editor-title"></span>
287 </span>
288 <span>
289 <button id="recompile" type="button">Recompile</button>
290 <button id="revert" type="button">Revert</button>
291 </span>
292 </div>
293 <div class="editor-body">
294 <div class="editor-code">
295 <pre id="highlight" aria-hidden="true"></pre>
296 <textarea
297 id="source"
298 spellcheck="false"
299 autocomplete="off"
300 autocapitalize="off"
301 aria-label="model source (MATLAB)"
302 ></textarea>
303 </div>
304 <pre id="compiled"></pre>
305 </div>
306 </div>
307 <div class="cli">
308 <div class="cli-head">
309 <span>The same run on the desktop, through the same .m and the same kernels</span>
310 <button id="copycmd" type="button">Copy</button>
311 </div>
312 <code id="cmd"></code>
313 </div>
314 <p id="blurb"></p>
315 <p id="err"></p>
316 </main>
317 <script type="module" src="/src/main.ts"></script>
318 </body>
319</html>
moveopenescclose