/ concept-collection / surfacefun-interactive
Sign in
concept-collection / surfacefun-interactive
199 lines · 5.2 KBCodeBlameHistory
a65b3e2Interactive surfacefun mesh refinement (numbl project)Jeremy Magland 1import { useEffect, useMemo, useState, type CSSProperties } from "react";
2import { SurfView } from "./render/SurfView.js";
31eb018Add interactive vector field demo with arrow scale sliderclaude[bot] 3import type { SurfTrace, Quiver3Trace } from "./render/types.js";
a65b3e2Interactive surfacefun mesh refinement (numbl project)Jeremy Magland 4import { onData, onHostEvent, sendToMATLAB } from "./bridge.js";
6/** Mesh payload from the numbl script: one flat (column-major) x/y/z array per
7 * patch, each an `n x n` grid. Mirrors what refine_demo.m sends. */
8interface MeshData {
9 n: number;
10 x: number[][];
11 y: number[][];
12 z: number[][];
13 npatches: number;
14 level?: number;
15 maxLevel?: number;
31eb018Add interactive vector field demo with arrow scale sliderclaude[bot] 18/** Vector field payload: surface patches + sampled tangent vectors at patch centers. */
19interface VectorFieldData extends MeshData {
20 type: "vectorfield";
21 vectors: {
22 x: number[];
23 y: number[];
24 z: number[];
25 u: number[];
26 v: number[];
27 w: number[];
28 };
a65b3e2Interactive surfacefun mesh refinement (numbl project)Jeremy Magland 31function isMeshData(d: unknown): d is MeshData {
32 return (
33 !!d &&
34 typeof d === "object" &&
35 Array.isArray((d as MeshData).x) &&
36 typeof (d as MeshData).n === "number"
37 );
31eb018Add interactive vector field demo with arrow scale sliderclaude[bot] 40function isVectorFieldData(d: unknown): d is VectorFieldData {
41 return (
42 isMeshData(d) &&
43 (d as VectorFieldData).type === "vectorfield" &&
44 !!(d as VectorFieldData).vectors
45 );
a65b3e2Interactive surfacefun mesh refinement (numbl project)Jeremy Magland 48export function App() {
49 const [mesh, setMesh] = useState<MeshData | null>(null);
50 const [level, setLevel] = useState(0);
51 const [busy, setBusy] = useState(false);
31eb018Add interactive vector field demo with arrow scale sliderclaude[bot] 52 const [arrowScale, setArrowScale] = useState(0.15);
54 useEffect(() => {
55 const apply = (d: unknown) => {
56 if (!isMeshData(d)) return;
57 setMesh(d);
58 if (typeof d.level === "number") setLevel(d.level);
59 setBusy(false);
60 };
61 const offData = onData(apply);
62 const offMesh = onHostEvent("mesh", apply);
63 return () => {
64 offData();
65 offMesh();
66 };
67 }, []);
31eb018Add interactive vector field demo with arrow scale sliderclaude[bot] 69 const surfTraces = useMemo<SurfTrace[]>(() => {
a65b3e2Interactive surfacefun mesh refinement (numbl project)Jeremy Magland 70 if (!mesh) return [];
31eb018Add interactive vector field demo with arrow scale sliderclaude[bot] 71 return mesh.x.map((_, k) => ({
72 x: mesh.x[k],
73 y: mesh.y[k],
74 z: mesh.z[k],
75 rows: mesh.n,
76 cols: mesh.n,
77 }));
31eb018Add interactive vector field demo with arrow scale sliderclaude[bot] 80 const quiverTraces = useMemo<Quiver3Trace[]>(() => {
81 if (!isVectorFieldData(mesh)) return [];
82 const { vectors } = mesh;
83 return [
84 {
85 x: vectors.x,
86 y: vectors.y,
87 z: vectors.z,
88 u: vectors.u.map(v => v * arrowScale),
89 v: vectors.v.map(v => v * arrowScale),
90 w: vectors.w.map(v => v * arrowScale),
91 showArrowHead: true,
92 color: [0.85, 0.325, 0.098] as [number, number, number],
93 lineWidth: 1.5,
94 },
95 ];
96 }, [mesh, arrowScale]);
98 const isVectorField = isVectorFieldData(mesh);
a65b3e2Interactive surfacefun mesh refinement (numbl project)Jeremy Magland 99 const maxLevel = mesh?.maxLevel ?? 3;
101 const onSlider = (v: number) => {
102 setLevel(v);
103 setBusy(true);
104 sendToMATLAB("refine", v);
105 };
107 return (
108 <div style={rootStyle}>
109 {mesh ? (
111 surfTraces={surfTraces}
112 quiver3Traces={quiverTraces}
113 shading="faceted"
114 />
116 <div style={waitingStyle}>Waiting for mesh from the script…</div>
117 )}
119 <div style={panelStyle}>
121 <>
122 <div style={{ fontWeight: 600, marginBottom: 8 }}>
123 Vector field on surface
124 </div>
125 <label style={{ display: "block", fontSize: 13 }}>
126 Arrow scale: <b>{arrowScale.toFixed(2)}</b>
127 <input
128 type="range"
129 min={0.02}
130 max={0.5}
131 step={0.01}
132 value={arrowScale}
133 onChange={e => setArrowScale(Number(e.target.value))}
134 style={{ width: "100%", marginTop: 4 }}
135 />
136 </label>
137 <div style={{ fontSize: 12, color: "#475569", marginTop: 4 }}>
138 patches: {mesh?.npatches ?? "—"}
139 </div>
140 </>
141 ) : (
142 <>
143 <div style={{ fontWeight: 600, marginBottom: 8 }}>
144 surfacefun mesh
145 </div>
146 <label style={{ display: "block", fontSize: 13 }}>
147 Refinement level: <b>{level}</b>
148 <input
149 type="range"
150 min={0}
151 max={maxLevel}
152 step={1}
153 value={level}
154 onChange={e => onSlider(Number(e.target.value))}
155 style={{ width: "100%", marginTop: 4 }}
156 />
157 </label>
158 <div style={{ fontSize: 12, color: "#475569", marginTop: 4 }}>
159 patches: {mesh?.npatches ?? "—"}
160 {busy ? " · refining…" : ""}
161 </div>
162 </>
163 )}
a65b3e2Interactive surfacefun mesh refinement (numbl project)Jeremy Magland 164 <div style={{ fontSize: 11, color: "#94a3b8", marginTop: 6 }}>
165 drag to rotate · scroll to zoom
166 </div>
167 </div>
168 </div>
169 );
172const rootStyle: CSSProperties = {
173 position: "absolute",
174 inset: 0,
175 background: "#ffffff",
176 fontFamily: "system-ui, -apple-system, Arial, sans-serif",
177};
179const waitingStyle: CSSProperties = {
180 position: "absolute",
181 inset: 0,
182 display: "flex",
183 alignItems: "center",
184 justifyContent: "center",
185 color: "#94a3b8",
186};
188const panelStyle: CSSProperties = {
189 position: "absolute",
190 top: 12,
191 left: 12,
192 width: 220,
193 padding: "12px 14px",
194 background: "rgba(255,255,255,0.92)",
195 border: "1px solid #e2e8f0",
196 borderRadius: 8,
197 boxShadow: "0 1px 4px rgba(0,0,0,0.1)",
198 color: "#0f172a",
199};
moveopenescclose