import * as THREE from 'three' import * as React from 'react' import { extend, useFrame, ReactThreeFiber } from '@react-three/fiber' import { Line2, LineSegmentsGeometry, LineMaterial, LineMaterialParameters } from 'three-stdlib' import { ForwardRefComponent } from '../helpers/ts-utils' type SegmentsProps = LineMaterialParameters & { limit?: number lineWidth?: number children: React.ReactNode } type Api = { subscribe: (ref: React.RefObject) => void } type SegmentRef = React.RefObject type SegmentProps = Omit & { start: ReactThreeFiber.Vector3 end: ReactThreeFiber.Vector3 color?: ReactThreeFiber.Color } const context = /* @__PURE__ */ React.createContext(null!) const Segments: ForwardRefComponent = /* @__PURE__ */ React.forwardRef( (props, forwardedRef) => { React.useMemo(() => extend({ SegmentObject }), []) const { limit = 1000, lineWidth = 1.0, children, ...rest } = props const [segments, setSegments] = React.useState>([]) const [line] = React.useState(() => new Line2()) const [material] = React.useState(() => new LineMaterial()) const [geometry] = React.useState(() => new LineSegmentsGeometry()) const [resolution] = React.useState(() => new THREE.Vector2(512, 512)) const [positions] = React.useState(() => Array(limit * 6).fill(0)) const [colors] = React.useState(() => Array(limit * 6).fill(0)) const api: Api = React.useMemo( () => ({ subscribe: (ref: React.RefObject) => { setSegments((segments) => [...segments, ref]) return () => setSegments((segments) => segments.filter((item) => item.current !== ref.current)) }, }), [] ) useFrame(() => { for (let i = 0; i < limit; i++) { const segment = segments[i]?.current if (segment) { positions[i * 6 + 0] = segment.start.x positions[i * 6 + 1] = segment.start.y positions[i * 6 + 2] = segment.start.z positions[i * 6 + 3] = segment.end.x positions[i * 6 + 4] = segment.end.y positions[i * 6 + 5] = segment.end.z colors[i * 6 + 0] = segment.color.r colors[i * 6 + 1] = segment.color.g colors[i * 6 + 2] = segment.color.b colors[i * 6 + 3] = segment.color.r colors[i * 6 + 4] = segment.color.g colors[i * 6 + 5] = segment.color.b } } geometry.setColors(colors) geometry.setPositions(positions) line.computeLineDistances() }) return ( {children} ) } ) declare global { namespace JSX { interface IntrinsicElements { segmentObject: ReactThreeFiber.Object3DNode } } } export class SegmentObject { color: THREE.Color start: THREE.Vector3 end: THREE.Vector3 constructor() { this.color = new THREE.Color('white') this.start = new THREE.Vector3(0, 0, 0) this.end = new THREE.Vector3(0, 0, 0) } } const normPos = (pos: SegmentProps['start']): SegmentObject['start'] => pos instanceof THREE.Vector3 ? pos : new THREE.Vector3(...(typeof pos === 'number' ? [pos, pos, pos] : pos)) const Segment: ForwardRefComponent = /* @__PURE__ */ React.forwardRef< SegmentObject, SegmentProps >(({ color, start, end }, forwardedRef) => { const api = React.useContext(context) if (!api) throw 'Segment must used inside Segments component.' const ref = React.useRef(null!) React.useImperativeHandle(forwardedRef, () => ref.current, []) React.useLayoutEffect(() => api.subscribe(ref), []) return }) export { Segments, Segment }