Split low-level plumbing into mesh_refiner.m
refine_demo.m is now a clean user script (load surfacefun, build a mesh, call
mesh_refiner). mesh_refiner(dom, 'MaxLevel', L) holds all the wiring: loads the
figure app, sends the mesh, and refines it on each slider change. It locates the
app via a cwd-relative path so no mfilename is needed.
3 changed files+76−66
README.mdmodified+4−3View file
@@ -14,9 +14,10 @@ and the slider drives work back in the interpreter, figure → script.
1414
1515 ## How it works
1616
17-- [`refine_demo.m`](refine_demo.m) — loads surfacefun via `mip`, builds the base
18- mesh, sends it to the figure, and refines on demand in its
19- `HTMLEventReceivedFcn` callback.
17+- [`refine_demo.m`](refine_demo.m) — the whole user-facing script: load
18+ surfacefun, build a mesh, and hand it to `mesh_refiner`.
19+- `mesh_refiner.m` — the plumbing: opens the figure, sends the mesh to it, and
20+ refines the mesh on each slider change (the script → figure → script wiring).
2021 - `app/` — a small React/three.js app (built to a single HTML file) that renders
2122 the surface with numbl's own surface renderer and hosts the slider.
2223
mesh_refiner.madded+66−0View file
@@ -0,0 +1,66 @@
1+function mesh_refiner(dom, varargin)
2+%MESH_REFINER Interactive refinement figure for a surfacemesh.
3+% MESH_REFINER(DOM) opens a figure that renders the surfacemesh DOM and shows
4+% a slider that refines it interactively. MESH_REFINER(DOM, 'MaxLevel', L)
5+% sets the maximum refinement level (default 3).
6+%
7+% This holds all the wiring: it loads the prebuilt figure app, sends the mesh
8+% to it (script -> figure), and refines the mesh whenever the slider in the
9+% figure asks for a new level (figure -> script). The end-user script only
10+% has to build a domain and call this.
11+
12+maxLevel = 3;
13+for i = 1:2:numel(varargin) - 1
14+ if strcmpi(varargin{i}, 'MaxLevel')
15+ maxLevel = varargin{i + 1};
16+ end
17+end
18+
19+% The figure app is the prebuilt single-file page, relative to the project root
20+% (the current working directory when a top-level script is run).
21+html = fileread(fullfile('app', 'dist', 'index.html'));
22+
23+fig = figure;
24+gl = uigridlayout(fig, [1 1], 'Padding', [0 0 0 0], ...
25+ 'RowHeight', {'1x'}, 'ColumnWidth', {'1x'});
26+uihtml(gl, 'HTMLSource', html, 'Data', mesh_to_data(dom, 0, maxLevel), ...
27+ 'HTMLEventReceivedFcn', @(src, ev) on_refine(src, ev, dom, maxLevel));
28+end
29+
30+function on_refine(src, ev, dom0, maxLevel)
31+% Slider in the figure -> refine the base mesh to the requested level and send
32+% the new patches back to the figure.
33+if ~strcmp(ev.HTMLEventName, 'refine')
34+ return
35+end
36+level = max(0, min(maxLevel, round(ev.HTMLEventData)));
37+if level == 0
38+ dom = dom0;
39+else
40+ dom = refine(dom0, level);
41+end
42+sendEventToHTMLSource(src, 'mesh', mesh_to_data(dom, level, maxLevel));
43+end
44+
45+function data = mesh_to_data(dom, level, maxLevel)
46+% Pack a surfacemesh into a plain struct the figure app can render: one flat
47+% (column-major) x/y/z array per patch, each an n-by-n grid. real() drops any
48+% all-zero imaginary part the solver may carry.
49+np = length(dom);
50+px = cell(1, np);
51+py = cell(1, np);
52+pz = cell(1, np);
53+for k = 1:np
54+ px{k} = real(dom.x{k}(:).');
55+ py{k} = real(dom.y{k}(:).');
56+ pz{k} = real(dom.z{k}(:).');
57+end
58+data = struct();
59+data.n = size(dom.x{1}, 1);
60+data.x = px;
61+data.y = py;
62+data.z = pz;
63+data.npatches = np;
64+data.level = level;
65+data.maxLevel = maxLevel;
66+end
refine_demo.mmodified+6−63View file
@@ -1,68 +1,11 @@
1-function refine_demo
2-%REFINE_DEMO Interactive surfacefun mesh refinement via a uihtml figure.
1+% Interactive surfacefun mesh refinement.
32 %
4-% Runs in the numbl IDE and via `numbl run --plot`. Shows a cubed-sphere
5-% surfacemesh rendered with numbl's surface renderer (duplicated into the
6-% bundled web app). Drag the "Refinement level" slider in the figure: the
7-% page sends the level to this script, which refines the base mesh with
8-% surfacefun and sends the new patches back — the figure re-renders without
9-% losing the camera orientation.
10-%
11-% The web app is prebuilt into app/dist/index.html (cd app && npm run build).
3+% Build a surface mesh (the "domain"), then hand it to mesh_refiner, which opens
4+% an interactive figure with a slider that refines the mesh live. Drag the
5+% slider to refine; drag the surface to rotate, scroll to zoom.
126
137 mip load --install flatironinstitute/flatironinstitute/surfacefun
148
15-here = fileparts(mfilename('fullpath'));
16-html = fileread(fullfile(here, 'app', 'dist', 'index.html'));
17-
18-maxLevel = 3; % slider range 0..maxLevel
19-n = 8; % Chebyshev order per patch
20-dom0 = surfacemesh.sphere(n); % base cubed sphere (6 patches)
21-
22-data0 = meshToData(dom0, 0, maxLevel);
23-
24-fig = figure;
25-gl = uigridlayout(fig, [1 1], 'Padding', [0 0 0 0], ...
26- 'RowHeight', {'1x'}, 'ColumnWidth', {'1x'});
27-uihtml(gl, 'HTMLSource', html, 'Data', data0, ...
28- 'HTMLEventReceivedFcn', @(src, ev) onRefine(src, ev, dom0, maxLevel));
29-end
30-
31-function onRefine(src, ev, dom0, maxLevel)
32-% Refine the base mesh to the requested absolute level and send it back.
33-if ~strcmp(ev.HTMLEventName, 'refine')
34- return
35-end
36-level = max(0, min(maxLevel, round(ev.HTMLEventData)));
37-if level == 0
38- dom = dom0;
39-else
40- dom = refine(dom0, level);
41-end
42-sendEventToHTMLSource(src, 'mesh', meshToData(dom, level, maxLevel));
43-fprintf('refined to level %d: %d patches\n', level, length(dom));
44-end
9+dom = surfacemesh.sphere(8); % a cubed-sphere surfacemesh
4510
46-function data = meshToData(dom, level, maxLevel)
47-% Pack a surfacemesh into a plain struct the web app can render: one flat
48-% (column-major) x/y/z array per patch, each an n-by-n grid.
49-np = length(dom);
50-px = cell(1, np);
51-py = cell(1, np);
52-pz = cell(1, np);
53-for k = 1:np
54- % real() drops any (all-zero) imaginary part the solver/JIT may carry;
55- % the geometry is real and jsonencode (numbl and MATLAB) rejects complex.
56- px{k} = real(dom.x{k}(:).');
57- py{k} = real(dom.y{k}(:).');
58- pz{k} = real(dom.z{k}(:).');
59-end
60-data = struct();
61-data.n = size(dom.x{1}, 1);
62-data.x = px;
63-data.y = py;
64-data.z = pz;
65-data.npatches = np;
66-data.level = level;
67-data.maxLevel = maxLevel;
68-end
11+mesh_refiner(dom, 'MaxLevel', 3);