UNPKG

react-three-fiber

Version:
144 lines (128 loc) 4.26 kB
import * as THREE from 'three' import { useRef, useContext, useEffect, useLayoutEffect, useMemo, useState } from 'react' import { SharedCanvasContext, RenderCallback, stateContext } from './canvas' //@ts-ignore import usePromise from 'react-promise-suspense' // helper type for omitting properties from types type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>> export function useFrame(callback: RenderCallback, renderPriority: number = 0): void { const { subscribe } = useContext(stateContext) // Update ref const ref = useRef<RenderCallback>(callback) useLayoutEffect(() => void (ref.current = callback), [callback]) // Subscribe/unsub useEffect(() => { const unsubscribe = subscribe(ref, renderPriority) return () => unsubscribe() }, [renderPriority]) } export function useRender(callback: RenderCallback, takeOver: boolean) { return useFrame(callback, takeOver ? 1 : 0) } export function useThree(): SharedCanvasContext { return useContext(stateContext) } export function useUpdate<T>( callback: (props: T) => void, dependents: any[], optionalRef?: React.MutableRefObject<T> ): React.MutableRefObject<any> { const { invalidate } = useContext(stateContext) const localRef = useRef() const ref = optionalRef ? optionalRef : localRef useEffect(() => { if (ref.current) { callback(ref.current) invalidate() } }, dependents) return ref } export function useResource<T>(optionalRef?: React.MutableRefObject<T>): [React.MutableRefObject<T>, T] { const [_, forceUpdate] = useState(false) const localRef = useRef<T>((undefined as unknown) as T) const ref = optionalRef ? optionalRef : localRef useEffect(() => void forceUpdate(i => !i), [ref.current]) return [ref, ref.current] } type Content = { geometry: THREE.Geometry | THREE.BufferGeometry material: THREE.Material | THREE.Material[] } type Extensions = (loader: THREE.Loader) => void type LoaderData = { data: any objects: any[] } const blackList = [ 'id', 'uuid', 'type', 'children', 'parent', 'matrix', 'matrixWorld', 'matrixWorldNeedsUpdate', 'modelViewMatrix', 'normalMatrix', ] function prune(props: any) { const reducedProps = { ...props } // Remove black listed props blackList.forEach(name => delete reducedProps[name]) // Remove functions Object.keys(reducedProps).forEach(name => typeof reducedProps[name] === 'function' && delete reducedProps[name]) // Prune materials and geometries if (reducedProps.material) reducedProps.material = prune(reducedProps.material) if (reducedProps.geometry) reducedProps.geometry = prune(reducedProps.geometry) // Return cleansed object return reducedProps } export function useLoader<T>(Proto: THREE.Loader, url: string | string[], extensions?: Extensions): T { const loader = useMemo(() => { // Construct new loader const temp = new (Proto as any)() // Run loader extensions if (extensions) extensions(temp) return temp }, [Proto]) // Use suspense to load async assets let results = usePromise<LoaderData>( (Proto: THREE.Loader, url: string | string[]) => { const urlArray = Array.isArray(url) ? url : [url] return Promise.all( urlArray.map( url => new Promise(res => loader.load(url, (data: any) => { const objects: any[] = [] if (data.scene) data.scene.traverse((props: any) => objects.push(prune(props))) data.__$ = objects res(data) }) ) ) ) }, [Proto, url] ) // Dispose objects on unmount useEffect(() => () => results.forEach((data: any) => { if (data.dispose) data.dispose() if (data.scene) data.scene.dispose() }, []) ) // Temporary hack to make the new api backwards compatible for a while ... const isArray = Array.isArray(url) if (!isArray) { Object.assign(results[0], { [Symbol.iterator]() { console.warn('[value]=useLoader(...) is deprecated, please use value=useLoader(...) instead!') return [results[0]][Symbol.iterator]() }, }) } // Return the object itself and a list of pruned props return isArray ? results : results[0] }