import * as React from 'react' import { Texture as _Texture, TextureLoader } from 'three' import { useLoader, useThree } from '@react-three/fiber' import { useLayoutEffect, useEffect, useMemo } from 'react' export const IsObject = (url: unknown): url is Record => url === Object(url) && !Array.isArray(url) && typeof url !== 'function' type TextureArray = T extends string[] ? _Texture[] : never type TextureRecord = T extends Record ? { [key in keyof T]: _Texture } : never type SingleTexture = T extends string ? _Texture : never export type MappedTextureType> = | TextureArray | TextureRecord | SingleTexture export function useTexture>( input: Url, onLoad?: (texture: MappedTextureType) => void ): MappedTextureType { const gl = useThree((state) => state.gl) const textures = useLoader(TextureLoader, IsObject(input) ? Object.values(input) : input) as MappedTextureType useLayoutEffect(() => { onLoad?.(textures) }, [onLoad]) // https://github.com/mrdoob/three.js/issues/22696 // Upload the texture to the GPU immediately instead of waiting for the first render // NOTE: only available for WebGLRenderer useEffect(() => { if ('initTexture' in gl) { let textureArray: _Texture[] = [] if (Array.isArray(textures)) { textureArray = textures } else if (textures instanceof _Texture) { textureArray = [textures] } else if (IsObject(textures)) { textureArray = Object.values(textures) } textureArray.forEach((texture) => { if (texture instanceof _Texture) { gl.initTexture(texture) } }) } }, [gl, textures]) const mappedTextures = useMemo(() => { if (IsObject(input)) { const keyed = {} as MappedTextureType let i = 0 for (const key in input) keyed[key] = textures[i++] return keyed } else { return textures } }, [input, textures]) return mappedTextures } useTexture.preload = (url: string | string[]) => useLoader.preload(TextureLoader, url) useTexture.clear = (input: string | string[]) => useLoader.clear(TextureLoader, input) // export const Texture = ({ children, input, onLoad, }: { children?: (texture: ReturnType) => React.ReactNode input: Parameters[0] onLoad?: Parameters[1] }) => { const ret = useTexture(input, onLoad) return <>{children?.(ret)} }