UNPKG

@playcanvas/react

Version:

A React renderer for PlayCanvas – build interactive 3D applications using React's declarative paradigm.

219 lines 7.18 kB
import { useState, useEffect, useRef, useCallback } from "react"; import { fetchAsset } from "../utils/fetch-asset.js"; import { useApp } from "./use-app.js"; import { dracoInitialize, TEXTURETYPE_RGBP } from "playcanvas"; import { warnOnce } from "../utils/validation.js"; const base = "https://www.gstatic.com/draco/versioned/decoders/1.5.7/"; dracoInitialize({ jsUrl: base + 'draco_wasm_wrapper.js', wasmUrl: base + 'draco_decoder.wasm', numWorkers: 2, lazyInit: true }); /** * Supported asset types that can be loaded */ const supportedTypes = ['texture', 'gsplat', 'container', 'model']; /** * Simple hook to fetch an asset from the asset registry. * * @param src - The source URL of the asset. * @param type - The type of the asset (must be one of: texture, gsplat, container, model). * @param props - Additional properties to pass to the asset loader. * @returns An object containing the asset, loading state, and any error. * * @example * ```tsx * const { asset, loading, error } = useAsset('model.glb', 'container'); * * if (loading) return <LoadingSpinner />; * if (error) return <ErrorMessage message={error} />; * if (!asset) return null; * * return <Render asset={asset} />; * ``` */ export const useAsset = (src, type, props = {}) => { const app = useApp(); const subscribersRef = useRef(new Set()); const subscribe = useCallback((cb) => { subscribersRef.current.add(cb); return () => subscribersRef.current.delete(cb); }, []); const [result, setResult] = useState({ asset: null, loading: true, error: null }); let stablePropsKey = null; try { stablePropsKey = JSON.stringify({ props }, Object.keys({ props }).sort()); } catch { const error = `Invalid props for "useAsset('${src}')". \`props\` must be serializable to JSON.`; warnOnce(error); setResult({ asset: null, loading: false, error }); } const onProgress = useCallback((meta) => { for (const cb of subscribersRef.current) cb(meta); }, []); useEffect(() => { if (stablePropsKey === null) return; // Reset state when inputs change setResult({ asset: null, loading: true, error: null }); // Validate inputs if (!src) { warnOnce("Asset source URL is required"); setResult({ asset: null, loading: false, error: "Asset source URL is required" }); return; } if (!app) { warnOnce("PlayCanvas application not found"); setResult({ asset: null, loading: false, error: "PlayCanvas application not found" }); return; } if (!supportedTypes.includes(type)) { warnOnce(`Unsupported asset type: "${type}". Supported types are "${supportedTypes.join('", "')}"`); setResult({ asset: null, loading: false, error: `Unsupported asset type: ${type}` }); return; } // Load the asset fetchAsset({ app, url: src, type, props, onProgress }) .then((asset) => { setResult({ asset: asset, loading: false, error: null }); }) .catch((error) => { warnOnce(`Failed to load asset: ${src}`); setResult({ asset: null, loading: false, error: error?.message || `Failed to load asset: ${src}`, }); }); }, [app, src, type, stablePropsKey]); return { ...result, subscribe }; }; /** * Simple hook to fetch a splat asset from the asset registry. * * @param src - The source URL of the splat asset. * @param props - Additional properties to pass to the asset loader. * @returns An object containing the asset, loading state, and any error. * * @example * ```tsx * const { asset, loading, error } = useSplat('splat.ply'); * * if (loading) return <LoadingSpinner />; * if (error) return <ErrorMessage message={error} />; * if (!asset) return null; * * return <GSplat asset={asset} />; * ``` */ export const useSplat = (src, props = {}) => useAsset(src, 'gsplat', props); /** * Simple hook to fetch a texture asset from the asset registry. * * @param src - The source URL of the texture asset. * @param props - Additional properties to pass to the asset loader. * @returns An object containing the asset, loading state, and any error. * * @example * ```tsx * const { asset, loading, error } = useTexture('texture.jpg'); * * if (loading) return <LoadingSpinner />; * if (error) return <ErrorMessage message={error} />; * if (!asset) return null; * * return <Material map={asset.resource} />; * ``` */ export const useTexture = (src, props = {}) => useAsset(src, 'texture', props); /** * Simple hook to load an environment atlas texture asset. * * @param src - The source URL of the environment atlas texture. * @param props - Additional properties to pass to the asset loader. * @returns An object containing the asset, loading state, and any error. * * @example * ```tsx * const { asset, loading, error } = useEnvAtlas('env.jpg'); * * if (loading) return <LoadingSpinner />; * if (error) return <ErrorMessage message={error} />; * if (!asset) return null; * * return <Environment envAtlas={asset} />; * ``` */ export const useEnvAtlas = (src, props = {}) => useAsset(src, 'texture', { ...props, data: { type: TEXTURETYPE_RGBP, mipmaps: false, ...(props.data ?? {}) } }); /** * Simple hook to load a 3D model asset (GLB/GLTF). * * @param src - The source URL of the 3D model. * @param props - Additional properties to pass to the asset loader. * @returns An object containing the asset, loading state, and any error. * * @example * ```tsx * const { asset, loading, error } = useModel('model.glb'); * * if (loading) return <LoadingSpinner />; * if (error) return <ErrorMessage message={error} />; * if (!asset) return null; * * return <Container asset={asset} />; * ``` */ export const useModel = (src, props = {}) => useAsset(src, 'container', props); /** * Simple hook to load a font asset. * * Fonts are described SDF textures, so you'll need to convert your .ttf files to SDF textures. * into the correct format. * * Learn more about SDF textures {@link https://playcanvas-react.vercel.app/docs/api/hooks/use-asset#usefont}. * * @param src - The source URL of the font asset. * @param props - Additional properties to pass to the asset loader. * @returns An object containing the asset, loading state, and any error. * * @example * ```tsx * import roboto from '@assets/fonts/roboto.ttf?url'; * export const App = () => { * const { asset, loading, error } = useFont(roboto); * } * ``` */ export const useFont = (src, props = {}) => useAsset(src, 'font', props); //# sourceMappingURL=use-asset.js.map