import { useFrame, type ElementProps } from '@react-three/fiber' import { EffectComposerContext } from '@react-three/postprocessing' import { RenderPass } from 'postprocessing' import { useContext, useEffect, useMemo, useState, type FC } from 'react' import { Texture, type Data3DTexture } from 'three' import { DEFAULT_STBN_URL, STBNLoader } from '@takram/three-geospatial' import { AerialPerspectiveEffect, aerialPerspectiveEffectOptionsDefaults } from '../AerialPerspectiveEffect' import { AtmosphereContext } from './Atmosphere' import { separateProps } from './separateProps' function useLoadSTBNTexture( input?: string | Data3DTexture ): Data3DTexture | null { const loadedTexture = useMemo( () => typeof input === 'string' ? new STBNLoader().load(input) : undefined, [input] ) useEffect(() => { if (loadedTexture != null) { return () => { loadedTexture.dispose() } } }, [loadedTexture]) return (typeof input === 'string' ? loadedTexture : input) ?? null } export interface AerialPerspectiveProps extends Omit< ElementProps, 'stbnTexture' > { stbnTexture?: Data3DTexture | string } export const AerialPerspective: FC = ({ ref: forwardedRef, stbnTexture: stbnTextureProp = DEFAULT_STBN_URL, ...props }) => { const { textures, transientStates, ...contextProps } = useContext(AtmosphereContext) const [atmosphereParameters, { blendFunction, ...others }] = separateProps({ ...aerialPerspectiveEffectOptionsDefaults, ...contextProps, ...textures, ...props }) const context = useContext(EffectComposerContext) const { normalPass, camera } = context const geometryTexture = 'geometryPass' in context && context.geometryPass instanceof RenderPass && 'geometryTexture' in context.geometryPass && context.geometryPass.geometryTexture instanceof Texture ? context.geometryPass.geometryTexture : undefined const effect = useMemo( () => new AerialPerspectiveEffect(undefined, { blendFunction }), [blendFunction] ) useEffect(() => { return () => { effect.dispose() } }, [effect]) const [needsSTBN, setNeedsSTBN] = useState(false) useFrame(() => { if (transientStates != null) { effect.sunDirection.copy(transientStates.sunDirection) effect.moonDirection.copy(transientStates.moonDirection) effect.worldToECEFMatrix.copy(transientStates.worldToECEFMatrix) effect.overlay = transientStates.overlay effect.shadow = transientStates.shadow effect.shadowLength = transientStates.shadowLength effect.lightingMask = transientStates.lightingMask // Load STBN only when the shadow is first enabled. if (!needsSTBN && effect.shadow != null) { setNeedsSTBN(true) } } }) const stbnTexture = useLoadSTBNTexture( needsSTBN ? stbnTextureProp : undefined ) return ( ) }