Download src/FlyLabels.tsx from gpue/flyyt: direct link, hf CLI and curl.
- Browser
- Download file 2.33 kB
-
https://huggingface.co/spaces/gpue/flyyt/resolve/main/src/FlyLabels.tsx
- Command line
-
hf download hf://spaces/gpue/flyyt/src/FlyLabels.tsx
-
curl -L -o FlyLabels.tsx https://huggingface.co/spaces/gpue/flyyt/resolve/main/src/FlyLabels.tsx
2.33 kB
| import { Billboard, Text } from "@react-three/drei"; | |
| import { useFrame } from "@react-three/fiber"; | |
| import { useRef } from "react"; | |
| import type { Group } from "three"; | |
| import { FLY_EXTENT_MM, type FlyInstanceData } from "./useFlyLayout"; | |
| import type { LiveFlyPositions } from "./useLiveFlyState"; | |
| // A fixed, room-scale size (NOT derived from the fly's own tiny body | |
| // extent) -- at the room's scale (thousands of mm across), a label sized | |
| // proportionally to a ~4mm fly is sub-millimeter and unreadable from any | |
| // normal viewing distance. Just above the fly's own bounding box, so the | |
| // label clears the body/wings instead of overlapping them. | |
| const LABEL_HEIGHT_MM = FLY_EXTENT_MM[1] + 15; | |
| const FONT_SIZE_MM = 50; | |
| interface FlyLabelsProps { | |
| flies: FlyInstanceData[]; | |
| livePositionsRef: React.RefObject<LiveFlyPositions>; | |
| } | |
| // Own small component (not folded into FlyInstances.tsx) so its per-frame | |
| // position sync stays simple: only the selected fly ever moves, but every | |
| // label still needs to track its own fly's live x/y/z each frame the same | |
| // way, driven off the same livePositionsRef the rest of the scene reads. | |
| export default function FlyLabels({ flies, livePositionsRef }: FlyLabelsProps) { | |
| const groupRefs = useRef(new Map<string, Group>()); | |
| useFrame(() => { | |
| const livePositions = livePositionsRef.current; | |
| for (const fly of flies) { | |
| const group = groupRefs.current.get(fly.id); | |
| const live = livePositions.get(fly.id); | |
| if (!group || !live) continue; | |
| group.position.set(live.x, live.y + LABEL_HEIGHT_MM, live.z); | |
| } | |
| }); | |
| return ( | |
| <> | |
| {flies.map((fly) => ( | |
| <group | |
| key={fly.id} | |
| ref={(group: Group | null) => { | |
| if (group) groupRefs.current.set(fly.id, group); | |
| else groupRefs.current.delete(fly.id); | |
| }} | |
| position={[fly.position[0], fly.position[1] + LABEL_HEIGHT_MM, fly.position[2]]} | |
| > | |
| <Billboard> | |
| <Text | |
| fontSize={FONT_SIZE_MM} | |
| color="#e8e9ee" | |
| outlineWidth={FONT_SIZE_MM * 0.08} | |
| outlineColor="#0b0c10" | |
| anchorX="center" | |
| anchorY="bottom" | |
| > | |
| Fly {fly.index + 1} | |
| </Text> | |
| </Billboard> | |
| </group> | |
| ))} | |
| </> | |
| ); | |
| } | |