diff --git a/manifold/src/console/ConsoleApp.tsx b/manifold/src/console/ConsoleApp.tsx index 5cd3a12..e6d6912 100644 --- a/manifold/src/console/ConsoleApp.tsx +++ b/manifold/src/console/ConsoleApp.tsx @@ -719,12 +719,7 @@ export function ConsoleApp({ focus: initialFocus = 'composite' }: ConsoleAppProp }} > {outputMode === 'particles' ? ( - setAxes((s) => ({ ...s, [k]: v }))} - /> + ) : focus === 'composite' ? ( void; - axes: Axes; - setAxis: (k: keyof Axes, v: number) => void; } -export function ParticleStage({ pos, onMove, axes, setAxis }: ParticleStageProps) { +export function ParticleStage({ pos, onMove }: ParticleStageProps) { const engine = useEngine(); const canvasRef = useRef(null); const vizRef = useRef(null); + const barsRef = useRef<(HTMLDivElement | null)[]>([]); + const tooltipRef = useRef(null); + const outputsRef = useRef(null); + const hoverRef = useRef(null); + const [hover, setHover] = useState(null); useEffect(() => { const canvas = canvasRef.current; @@ -40,7 +51,20 @@ export function ParticleStage({ pos, onMove, axes, setAxis }: ParticleStageProps let raf = 0; const tick = () => { const outputs = engine?.getOutputs(); - if (outputs) viz.setParams(outputs); + if (outputs) { + outputsRef.current = outputs; + viz.setParams(outputs); + // Drive the heatmap bar widths imperatively (cheap; no React churn). + for (let i = 0; i < N_VISUAL_OUTPUTS; i++) { + const bar = barsRef.current[i]; + if (bar) bar.style.width = `${Math.max(0, Math.min(1, outputs[i] ?? 0)) * 100}%`; + } + // Keep the tooltip value live while hovering a cell. + const h = hoverRef.current; + if (h != null && tooltipRef.current) { + tooltipRef.current.textContent = `${VISUAL_PARAM_NAMES[h]}: ${(outputs[h] ?? 0).toFixed(3)}`; + } + } viz.draw(); raf = requestAnimationFrame(tick); }; @@ -64,7 +88,7 @@ export function ParticleStage({ pos, onMove, axes, setAxis }: ParticleStageProps style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', display: 'block' }} /> - {/* Top horizontal macro-axis slider bar (Boldness / Memory / Precision) */} + {/* Top heatmap strip — one colored bar per visual output, width = value */}
{ + hoverRef.current = null; + setHover(null); }} > - - MEMLNaut - - setAxis('boldness', v)} - style={{ flex: 1 }} - /> - setAxis('memory', v)} - accent="var(--accent-2)" - style={{ flex: 1 }} - /> - setAxis('precision', v)} - accent="var(--ok, var(--accent))" - style={{ flex: 1 }} - /> + {VISUAL_PARAM_NAMES.map((name, i) => ( +
{ + hoverRef.current = i; + setHover(i); + }} + style={{ + position: 'relative', + flex: 1, + minWidth: 0, + height: 16, + background: 'rgba(255,255,255,0.04)', + overflow: 'hidden', + cursor: 'pointer', + }} + > +
{ + barsRef.current[i] = el; + }} + style={{ + height: '100%', + width: '30%', + background: VISUAL_PARAM_COLORS[i], + borderRadius: '0 1px 1px 0', + filter: hover === i ? 'brightness(1.3)' : 'none', + pointerEvents: 'none', + }} + /> +
+ ))} +
+ {/* Hover tooltip (name + live value) */} +
+ {/* Bottom-left circular pad — drives the 2D input */}
diff --git a/manifold/src/console/flow-field.ts b/manifold/src/console/flow-field.ts index 4c2f51a..4c231c5 100644 --- a/manifold/src/console/flow-field.ts +++ b/manifold/src/console/flow-field.ts @@ -69,6 +69,26 @@ function noise2D(x: number, y: number): number { const TWO_PI = Math.PI * 2; +/** + * The 20 visual output params, in output order (p0..p19). Names and colours are + * verbatim from the a-immersive original (`VISUAL_PARAM_NAMES` / + * `VISUAL_PARAM_COLORS`, a-app.js:46/51) so the heatmap strip matches it 1:1. + */ +export const VISUAL_PARAM_NAMES = [ + 'Flow', 'Scale', 'Speed', 'Hue', 'Spread', 'Size', 'Trail', 'Turb', + 'Attract', 'Radius', 'DispRate', 'DispAmt', 'Lifetime', 'Respawn', + 'Advection', 'Inertia', 'Drag', 'Repulse', 'RepCnt', 'RepRate', +] as const; + +export const VISUAL_PARAM_COLORS = [ + '#ff6a00', '#00ccff', '#ff6600', '#ff00cc', '#ffcc00', '#88ff00', + '#0088ff', '#ff3366', '#9bff5f', '#59d3ff', '#ff8f3f', '#a0b7ff', + '#f4ff7a', '#ffa8db', '#7dffc8', '#ffd166', '#8ad4ff', '#ff5f5f', + '#ffc15f', '#ff8a3d', +] as const; + +export const N_VISUAL_OUTPUTS = VISUAL_PARAM_NAMES.length; + interface Particle { x: number; y: number;