Skip to content

Commit 03e8982

Browse files
authored
Merge pull request #59 from AdaWorldAPI/claude/charming-johnson-ufstpw
fma: (place:tissue) render convergence + /fma-body cockpit + 1M SoA scan PoC
2 parents a972adc + 0575259 commit 03e8982

10 files changed

Lines changed: 1512 additions & 122 deletions

File tree

cockpit/public/fma_body.mesh

15.1 MB
Binary file not shown.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"source":"BodyParts3D 4.0 (DBCLS) is_a OBJ, vertex-cluster decimated","attribution":"BodyParts3D, (c) The Database Center for Life Science, CC-BY 4.0 / CC-BY-SA 2.1 JP","format":"SPM1; opacity byte = LAYER id (1 skin·2 muscle·3 organ·4 skeleton·5 vessel·6 nerve·7 connective·8 other)","verts":329478,"tris":744742,"cell_mm":3.6,"layers":{"vessel":496,"organ":270,"skeleton":231,"other":202,"muscle":57,"skin":2}}

cockpit/src/FmaBody.tsx

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
// /fma-body — MY full-body FMA viewer (additive to the other session's /torso*).
2+
//
3+
// Renders cockpit/public/fma_body.mesh (baked by `fma`'s cockpit_bake) — the same SPM1
4+
// indexed triangle surface the cockpit already decodes, but the per-vertex `opacity`
5+
// byte carries a clean LAYER id (1 skin · 2 muscle · 3 organ · 4 skeleton · 5 vessel ·
6+
// 6 nerve · 7 connective · 8 other). So the viewer can TOGGLE each layer with a button,
7+
// and switch the whole body between SOLID and TRANSPARENT. Color is the converged
8+
// `tissue` byte (is_a); geometry is real BodyParts3D, vertex-cluster decimated.
9+
//
10+
// This does not touch /torso, /torso-live, /torso-splat, /torso-map (#57/#58).
11+
//
12+
// Geometry/data: BodyParts3D, (c) The Database Center for Life Science, CC-BY 4.0.
13+
import { useEffect, useRef, useState } from 'react';
14+
import * as THREE from 'three';
15+
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
16+
17+
const PAGE_BG = 0x0a0e17;
18+
19+
// layer id (opacity byte) → label + swatch. id 0 unused; index = id.
20+
const LAYERS: { id: number; name: string; color: string }[] = [
21+
{ id: 1, name: 'skin', color: '#dba88a' },
22+
{ id: 2, name: 'muscle', color: '#bd5c57' },
23+
{ id: 3, name: 'organ', color: '#cc9484' },
24+
{ id: 4, name: 'skeleton', color: '#ebe0c7' },
25+
{ id: 5, name: 'vessel', color: '#cc3838' },
26+
{ id: 6, name: 'nerve', color: '#ebd152' },
27+
{ id: 7, name: 'connective', color: '#e0dbcc' },
28+
{ id: 8, name: 'other', color: '#9696a0' },
29+
];
30+
31+
interface Mesh {
32+
vertCount: number;
33+
triCount: number;
34+
positions: Float32Array;
35+
normals: Float32Array;
36+
colors: Uint8Array;
37+
layer: Float32Array; // per-vertex layer id (from the opacity byte)
38+
index: Uint32Array;
39+
}
40+
41+
// SPM1 (little-endian): header 40 B | vert 21 B [pos 3f|normal 3i8|rgb 3u8|opacity u8|
42+
// node_row u16] | index 12 B. Orientation (x,y,z)->(-x,z,y): proper rotation (det +1),
43+
// head-up in three.js Y-up — identical to TorsoMesh so both viewers agree.
44+
function decodeSpm1(buf: ArrayBuffer): Mesh {
45+
const dv = new DataView(buf);
46+
const magic = String.fromCharCode(dv.getUint8(0), dv.getUint8(1), dv.getUint8(2), dv.getUint8(3));
47+
if (magic !== 'SPM1') throw new Error(`bad magic "${magic}" (expected SPM1)`);
48+
const vertCount = dv.getUint32(4, true);
49+
const triCount = dv.getUint32(8, true);
50+
const voff = 40;
51+
const positions = new Float32Array(vertCount * 3);
52+
const normals = new Float32Array(vertCount * 3);
53+
const colors = new Uint8Array(vertCount * 3);
54+
const layer = new Float32Array(vertCount);
55+
for (let i = 0; i < vertCount; i++) {
56+
const b = voff + i * 21;
57+
const x = dv.getFloat32(b, true), y = dv.getFloat32(b + 4, true), z = dv.getFloat32(b + 8, true);
58+
positions[i * 3] = -x; positions[i * 3 + 1] = z; positions[i * 3 + 2] = y;
59+
normals[i * 3] = -dv.getInt8(b + 12) / 127;
60+
normals[i * 3 + 1] = dv.getInt8(b + 14) / 127;
61+
normals[i * 3 + 2] = dv.getInt8(b + 13) / 127;
62+
colors[i * 3] = dv.getUint8(b + 15);
63+
colors[i * 3 + 1] = dv.getUint8(b + 16);
64+
colors[i * 3 + 2] = dv.getUint8(b + 17);
65+
layer[i] = dv.getUint8(b + 18); // opacity byte = LAYER id
66+
}
67+
const ioff = voff + vertCount * 21;
68+
const index = new Uint32Array(triCount * 3);
69+
for (let t = 0; t < triCount; t++) {
70+
const b = ioff + t * 12;
71+
index[t * 3] = dv.getUint32(b, true);
72+
index[t * 3 + 1] = dv.getUint32(b + 4, true);
73+
index[t * 3 + 2] = dv.getUint32(b + 8, true);
74+
}
75+
return { vertCount, triCount, positions, normals, colors, layer, index };
76+
}
77+
78+
// Per-layer visibility via uEnabled[9] (indexed by the vertex layer id) + a global alpha
79+
// for the solid↔transparent switch. Phong smooth shade, two-sided.
80+
const VERT = `
81+
attribute vec3 aNormal;
82+
attribute vec3 aColor;
83+
attribute float aLayer;
84+
varying vec3 vNormal;
85+
varying vec3 vColor;
86+
varying float vLayer;
87+
void main() {
88+
vNormal = aNormal;
89+
vColor = aColor;
90+
vLayer = aLayer;
91+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
92+
}`;
93+
const FRAG = `
94+
precision mediump float;
95+
uniform float uEnabled[9];
96+
uniform float uAlpha;
97+
varying vec3 vNormal;
98+
varying vec3 vColor;
99+
varying float vLayer;
100+
void main() {
101+
int li = int(vLayer + 0.5);
102+
if (li < 0 || li > 8 || uEnabled[li] < 0.5) discard; // layer toggled off
103+
vec3 n = normalize(vNormal);
104+
if (!gl_FrontFacing) n = -n; // two-sided
105+
const vec3 L = vec3(-0.401, 0.783, 0.476);
106+
float ndl = max(dot(n, L), 0.0);
107+
float hemi = 0.34 + 0.20 * (n.y * 0.5 + 0.5);
108+
float fill = 0.12 * (-n.x * 0.5 + 0.5);
109+
float shade = min(hemi + fill + 0.92 * ndl, 1.3);
110+
gl_FragColor = vec4(vColor * shade, uAlpha);
111+
}`;
112+
113+
interface RenderState {
114+
enabled: Float32Array; // length 9, indexed by layer id
115+
alpha: number;
116+
transparent: boolean;
117+
}
118+
119+
function mount(container: HTMLDivElement, mesh: Mesh, st: RenderState, onStats: (s: { fps: number }) => void): () => void {
120+
let w = container.clientWidth || window.innerWidth;
121+
let h = container.clientHeight || window.innerHeight;
122+
123+
const scene = new THREE.Scene();
124+
scene.background = new THREE.Color(PAGE_BG);
125+
const camera = new THREE.PerspectiveCamera(45, w / h, 0.01, 100);
126+
camera.position.set(0, 0.05, 3.0);
127+
const renderer = new THREE.WebGLRenderer({ antialias: true });
128+
renderer.setSize(w, h);
129+
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
130+
container.appendChild(renderer.domElement);
131+
132+
const geom = new THREE.BufferGeometry();
133+
geom.setAttribute('position', new THREE.BufferAttribute(mesh.positions, 3));
134+
geom.setAttribute('aNormal', new THREE.BufferAttribute(mesh.normals, 3));
135+
geom.setAttribute('aColor', new THREE.BufferAttribute(mesh.colors, 3, true));
136+
geom.setAttribute('aLayer', new THREE.BufferAttribute(mesh.layer, 1));
137+
geom.setIndex(new THREE.BufferAttribute(mesh.index, 1));
138+
const mat = new THREE.ShaderMaterial({
139+
vertexShader: VERT,
140+
fragmentShader: FRAG,
141+
uniforms: { uEnabled: { value: st.enabled }, uAlpha: { value: st.alpha } },
142+
side: THREE.DoubleSide,
143+
transparent: st.transparent,
144+
depthWrite: !st.transparent,
145+
});
146+
const obj = new THREE.Mesh(geom, mat);
147+
scene.add(obj);
148+
149+
const controls = new OrbitControls(camera, renderer.domElement);
150+
controls.enableDamping = true;
151+
controls.dampingFactor = 0.08;
152+
controls.autoRotate = true;
153+
controls.autoRotateSpeed = 0.6;
154+
controls.target.set(0, 0, 0);
155+
controls.minDistance = 0.6;
156+
controls.maxDistance = 12;
157+
158+
let raf = 0;
159+
let ema = 16.6;
160+
let last = performance.now();
161+
let sinceStat = 0;
162+
let wasTransparent = st.transparent;
163+
const tick = () => {
164+
raf = requestAnimationFrame(tick);
165+
const now = performance.now();
166+
ema = ema * 0.9 + (now - last) * 0.1;
167+
last = now;
168+
const pr = ema > 30 ? 1 : Math.min(window.devicePixelRatio, 2);
169+
if (renderer.getPixelRatio() !== pr) renderer.setPixelRatio(pr);
170+
mat.uniforms.uEnabled.value = st.enabled;
171+
mat.uniforms.uAlpha.value = st.alpha;
172+
if (st.transparent !== wasTransparent) {
173+
mat.transparent = st.transparent;
174+
mat.depthWrite = !st.transparent;
175+
mat.needsUpdate = true;
176+
wasTransparent = st.transparent;
177+
}
178+
controls.update();
179+
renderer.render(scene, camera);
180+
if (++sinceStat >= 20) {
181+
sinceStat = 0;
182+
onStats({ fps: Math.round(1000 / Math.max(ema, 1)) });
183+
}
184+
};
185+
tick();
186+
187+
const onResize = () => {
188+
w = container.clientWidth || window.innerWidth;
189+
h = container.clientHeight || window.innerHeight;
190+
camera.aspect = w / h;
191+
camera.updateProjectionMatrix();
192+
renderer.setSize(w, h);
193+
};
194+
const ro = new ResizeObserver(onResize);
195+
ro.observe(container);
196+
197+
return () => {
198+
cancelAnimationFrame(raf);
199+
ro.disconnect();
200+
controls.dispose();
201+
geom.dispose();
202+
mat.dispose();
203+
renderer.dispose();
204+
if (renderer.domElement.parentNode === container) container.removeChild(renderer.domElement);
205+
};
206+
}
207+
208+
export function FmaBody() {
209+
const ref = useRef<HTMLDivElement>(null);
210+
const [mesh, setMesh] = useState<Mesh | null>(null);
211+
const [error, setError] = useState<string | null>(null);
212+
const [stats, setStats] = useState<{ fps: number } | null>(null);
213+
// skin off by default (so the anatomy shows); everything else on.
214+
const [on, setOn] = useState<Record<number, boolean>>({ 1: false, 2: true, 3: true, 4: true, 5: true, 6: true, 7: true, 8: true });
215+
const [transparent, setTransparent] = useState(false);
216+
// shared, mutation-friendly render state (read every frame by the GL loop).
217+
const stRef = useRef<RenderState>({ enabled: new Float32Array([0, 0, 1, 1, 1, 1, 1, 1, 1]), alpha: 1, transparent: false });
218+
219+
// push React state → the GL render state each change.
220+
useEffect(() => {
221+
const e = new Float32Array(9);
222+
for (let i = 1; i <= 8; i++) e[i] = on[i] ? 1 : 0;
223+
stRef.current.enabled = e;
224+
stRef.current.transparent = transparent;
225+
stRef.current.alpha = transparent ? 0.42 : 1.0;
226+
}, [on, transparent]);
227+
228+
useEffect(() => {
229+
let cancelled = false;
230+
fetch('/fma_body.mesh')
231+
.then((r) => { if (!r.ok) throw new Error(`HTTP ${r.status} fetching fma_body.mesh`); return r.arrayBuffer(); })
232+
.then((buf) => { if (!cancelled) setMesh(decodeSpm1(buf)); })
233+
.catch((e) => { if (!cancelled) setError(String(e)); });
234+
return () => { cancelled = true; };
235+
}, []);
236+
237+
useEffect(() => {
238+
const container = ref.current;
239+
if (!container || !mesh) return;
240+
return mount(container, mesh, stRef.current, setStats);
241+
}, [mesh]);
242+
243+
const btn = (active: boolean): React.CSSProperties => ({
244+
padding: '5px 11px',
245+
borderRadius: 6,
246+
border: `1px solid ${active ? '#5a7fa8' : '#2a3242'}`,
247+
background: active ? '#16202e' : '#0e1219',
248+
color: active ? '#cdd9e5' : '#6a7686',
249+
font: '12px ui-monospace, monospace',
250+
cursor: 'pointer',
251+
});
252+
253+
return (
254+
<div style={{ position: 'fixed', inset: 0, background: '#0a0e17', overflow: 'hidden' }}>
255+
<div ref={ref} style={{ position: 'absolute', inset: 0 }} />
256+
257+
<div style={{ position: 'absolute', top: 12, left: 16, color: '#cdd9e5', font: '13px ui-monospace, monospace', pointerEvents: 'none' }}>
258+
<div style={{ fontSize: 15, color: '#fff' }}>FMA body · (place:tissue) layers</div>
259+
<div style={{ opacity: 0.7 }}>
260+
{mesh ? `${mesh.triCount.toLocaleString()} triangles · drag to orbit` : error ? '' : 'loading fma_body.mesh…'}
261+
</div>
262+
{stats && <div style={{ opacity: 0.5, marginTop: 2 }}>{stats.fps} fps · solid surface, layer-gated by the converged key</div>}
263+
</div>
264+
265+
{/* layer toggles + solid/transparent */}
266+
<div style={{ position: 'absolute', top: 12, right: 16, display: 'flex', flexDirection: 'column', gap: 8, alignItems: 'flex-end' }}>
267+
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', justifyContent: 'flex-end', maxWidth: 360 }}>
268+
{LAYERS.map((l) => (
269+
<button key={l.id} style={btn(on[l.id])} onClick={() => setOn((p) => ({ ...p, [l.id]: !p[l.id] }))}>
270+
<span style={{ display: 'inline-block', width: 8, height: 8, borderRadius: 4, background: l.color, marginRight: 6, verticalAlign: 'middle' }} />
271+
{l.name}
272+
</button>
273+
))}
274+
</div>
275+
<button style={btn(transparent)} onClick={() => setTransparent((v) => !v)}>
276+
{transparent ? 'transparent' : 'solid'}
277+
</button>
278+
<div style={{ display: 'flex', gap: 14, font: '12px ui-monospace, monospace', marginTop: 2 }}>
279+
<a href="/torso-live" style={{ color: '#7fa6c4', textDecoration: 'none' }}>/torso-live →</a>
280+
<a href="/fma" style={{ color: '#7fa6c4', textDecoration: 'none' }}>/fma graph →</a>
281+
</div>
282+
</div>
283+
284+
{error && (
285+
<div style={{ position: 'absolute', top: '46%', width: '100%', textAlign: 'center', color: '#ff8095', font: '13px ui-monospace, monospace' }}>
286+
{error}
287+
<div style={{ opacity: 0.7, marginTop: 6 }}>
288+
bake: <code>cargo run -p fma --bin cockpit_bake -- &lt;parts&gt; &lt;element_parts&gt; &lt;converged.tsv&gt; cockpit/public/fma_body.mesh</code>
289+
</div>
290+
</div>
291+
)}
292+
293+
<div style={{ position: 'absolute', bottom: 10, left: 16, color: '#5a6b7e', font: '10px ui-monospace, monospace', maxWidth: '70%', pointerEvents: 'none' }}>
294+
BodyParts3D, (c) The Database Center for Life Science, licensed under CC Attribution 4.0 International
295+
</div>
296+
</div>
297+
);
298+
}

cockpit/src/main.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { TorsoMesh } from './TorsoMesh';
1212
import { TorsoSplat } from './TorsoSplat';
1313
import { TorsoRender } from './TorsoRender';
1414
import { TorsoMap } from './TorsoMap';
15+
import { FmaBody } from './FmaBody';
1516
import { ReasoningPage } from './ReasoningPage';
1617
import { ErrorBoundary } from './components/ErrorBoundary';
1718
import './styles/cockpit.css';
@@ -92,6 +93,10 @@ createRoot(document.getElementById('root')!).render(
9293
{/* FMA torso map — splat AS the GUID/value-tenant SoA: click a gaussian → its
9394
FMA node (O(1) switch into the node SoA) → label + partonomy ↔ graph */}
9495
<Route path="/torso-map" element={<TorsoMap />} />
96+
{/* MY full-body FMA viewer — solid triangle surface gated per (place:tissue)
97+
LAYER (skin/muscle/organ/skeleton/vessel/nerve buttons) + solid↔transparent.
98+
Additive; reads cockpit/public/fma_body.mesh; never touches /torso* (#57/#58). */}
99+
<Route path="/fma-body" element={<FmaBody />} />
95100
{/* The Palantir JSON-graph cockpit (221 aiwar nodes) stays reachable
96101
at /palantir and as the catch-all for its own sub-routes. */}
97102
<Route path="/palantir" element={<PalantirApp />} />

0 commit comments

Comments
 (0)