| import { Text } from "@react-three/drei"; |
| import { useFrame } from "@react-three/fiber"; |
| import { useMemo, useRef } from "react"; |
| import * as THREE from "three"; |
|
|
| import { latticeAxisOf } from "../spec.js"; |
|
|
| |
| |
| |
| |
| |
| const POOL = 56; |
| const RECHECK = 0.12; |
| const CAM_STEP = 0.4; |
| const MIN_SEP = 0.115; |
|
|
| const _v = new THREE.Vector3(); |
|
|
| export default function CellLabels({ parsed, layout, anim, enabled, cellData }) { |
| const refs = useRef([]); |
| const lastCam = useRef(new THREE.Vector3(Infinity, 0, 0)); |
| const since = useRef(0); |
| const shown = useRef([]); |
|
|
| |
| |
| const names = useMemo(() => { |
| const all = parsed.spec.lattice.names ?? []; |
| const spatial = all.slice(parsed.spec.lattice.time ? 1 : 0); |
| return parsed.shape.map((_, i) => spatial[i] ?? `dim${i}`); |
| }, [parsed]); |
|
|
| const positions = useMemo( |
| () => parsed.cells.map((c) => layout.position(c)), |
| [parsed, layout], |
| ); |
|
|
| const coordText = useMemo( |
| () => parsed.cells.map((c) => names.map((n, i) => `${n} ${c[i]}`).join("\n")), |
| [parsed, names], |
| ); |
|
|
| |
| |
| |
| |
| |
| const aligned = |
| cellData && |
| cellData.pred && |
| cellData.pred.length === parsed.cells.length; |
|
|
| useFrame((state, dt) => { |
| const pool = refs.current; |
| if (!pool.length) return; |
|
|
| if (!enabled) { |
| |
| if (shown.current.length) { |
| pool.forEach((t) => t && (t.visible = false)); |
| shown.current = []; |
| } |
| return; |
| } |
|
|
| const cam = state.camera; |
| since.current += dt; |
| const moved = cam.position.distanceTo(lastCam.current); |
| const restack = since.current > RECHECK || moved > CAM_STEP; |
|
|
| if (restack) { |
| since.current = 0; |
| lastCam.current.copy(cam.position); |
| |
| const scored = []; |
| for (let i = 0; i < positions.length; i++) { |
| const p = positions[i]; |
| _v.set(p[0], p[1], p[2]); |
| scored.push([_v.distanceTo(cam.position), i]); |
| } |
| scored.sort((a, b) => a[0] - b[0]); |
|
|
| |
| |
| |
| |
| |
| |
| |
| const kept = []; |
| for (const [dist, i] of scored) { |
| if (kept.length >= POOL) break; |
| const p = positions[i]; |
| _v.set(p[0], p[1], p[2]).project(cam); |
| if (_v.z > 1) continue; |
| let clear = true; |
| for (const k of kept) { |
| if (Math.hypot((_v.x - k.x) * 0.55, _v.y - k.y) < MIN_SEP) { |
| clear = false; |
| break; |
| } |
| } |
| if (clear) kept.push({ x: _v.x, y: _v.y, i, dist }); |
| } |
| shown.current = kept.map((k) => [k.dist, k.i]); |
| } |
|
|
| |
| |
| const { spec } = parsed; |
| const layer = spec.layers[anim.current.layer]; |
| const family = spec.nd_method.family; |
| const axis = |
| layer && family !== "kernel" && family !== "flatten" |
| ? latticeAxisOf(layer, spec) |
| : null; |
| const size = axis === null ? 0 : parsed.shape[axis]; |
| const front = |
| layer && layer.reverse |
| ? (1 - anim.current.progress) * (size - 1) |
| : anim.current.progress * (size - 1); |
|
|
| for (let k = 0; k < POOL; k++) { |
| const t = pool[k]; |
| if (!t) continue; |
| const entry = shown.current[k]; |
| if (!entry) { |
| t.visible = false; |
| continue; |
| } |
| const [dist, i] = entry; |
| const p = positions[i]; |
| t.visible = true; |
| t.position.set(p[0], p[1] + 0.52, p[2]); |
| t.quaternion.copy(cam.quaternion); |
| const label = aligned |
| ? `${coordText[i]}\npred ${cellData.pred[i]}\ntrue ${cellData.true[i]}` |
| : coordText[i]; |
| if (t.text !== label) t.text = label; |
|
|
| |
| const near = layout.radius * 1.1; |
| const far = layout.radius * 2.6; |
| t.fillOpacity = 1 - THREE.MathUtils.clamp((dist - near) / (far - near), 0, 0.92); |
|
|
| if (axis === null) { |
| t.color = "#9fb0cc"; |
| } else { |
| const d = parsed.cells[i][axis] - front; |
| const behind = layer.reverse ? d > 0 : d < 0; |
| t.color = Math.abs(d) < 0.75 ? "#ffd79a" : behind ? "#9fc4ff" : "#7d8aa3"; |
| } |
| } |
| }); |
|
|
| |
| |
| |
| const fontSize = Math.max(0.15, layout.radius * 0.032); |
|
|
| return ( |
| <group> |
| {Array.from({ length: POOL }, (_, i) => ( |
| <Text |
| key={i} |
| ref={(el) => (refs.current[i] = el)} |
| visible={false} |
| fontSize={fontSize} |
| lineHeight={1.25} |
| anchorX="center" |
| anchorY="bottom" |
| outlineWidth={0.014} |
| outlineColor="#080b11" |
| > |
| {""} |
| </Text> |
| ))} |
| </group> |
| ); |
| } |
|
|