UNPKG

@vctrl/hooks

Version:

vctrl/hooks is a React hooks package designed to simplify 3D model loading and management within React applications. It's part of the vectreal-core ecosystem and is primarily used in the vctrl/viewer React component and the official website application.

297 lines (296 loc) 8.32 kB
import { buildAssetLookupKeys as e, normalizeAssetUri as t, toSerializedAssetBytes as n } from "@vctrl/core"; import { useCallback as r, useEffect as i, useMemo as a, useReducer as o, useRef as s, useState as c } from "react"; import { ModelOptimizer as l } from "@vctrl/core/model-optimizer"; //#region packages/hooks/src/use-optimize-model/state.ts var u = { info: null, error: null, loading: !1, report: null }, d = (e, t) => { switch (t.type) { case "LOAD_START": return { ...e, loading: !0, error: null }; case "LOAD_SUCCESS": return { ...e, loading: !1, report: t.payload.report }; case "LOAD_ERROR": return { ...e, loading: !1, error: t.payload }; case "RESET": return { ...u }; default: return e; } }; //#endregion //#region packages/hooks/src/use-optimize-model/use-calc-optimization-info.ts function f(e) { return e ? { verticesCount: e.stats.vertices.after ?? 0, primitivesCount: e.stats.triangles.after ?? 0, meshesCount: e.stats.meshes.after ?? 0, textureAssetCount: e.stats.texturesCount.after ?? 0, materialsCount: e.stats.materials.after ?? 0, sceneBytes: e.optimizedSize ?? 0 } : { verticesCount: 0, primitivesCount: 0, meshesCount: 0, textureAssetCount: 0, materialsCount: 0, sceneBytes: 0 }; } var p = (e) => { let [t, n] = c(null), o = e.report; return i(() => { !t && o && n(o); }, [t, o]), { info: a(() => { let e = f(t), n = f(o); return { initial: { verticesCount: e.verticesCount, primitivesCount: e.primitivesCount, meshesCount: e.meshesCount, texturesCount: e.textureAssetCount, sceneBytes: e.sceneBytes }, optimized: { verticesCount: n.verticesCount, primitivesCount: n.primitivesCount, meshesCount: n.meshesCount, texturesCount: n.textureAssetCount, sceneBytes: n.sceneBytes }, improvement: { verticesCount: e.verticesCount - n.verticesCount, primitivesCount: e.primitivesCount - n.primitivesCount, meshesCount: e.meshesCount - n.meshesCount, texturesCount: e.textureAssetCount - n.textureAssetCount, sceneBytes: e.sceneBytes - n.sceneBytes } }; }, [o, t]), reset: r(() => { n(null); }, []) }; }; //#endregion //#region packages/hooks/src/use-optimize-model/utils/browser-texture-encoder.ts async function m(e, t) { let n = new Blob([e]), r = await createImageBitmap(n), { width: i, height: a } = r; if (t.maxWidth || t.maxHeight) { let e = t.maxWidth ? t.maxWidth / i : Infinity, n = t.maxHeight ? t.maxHeight / a : Infinity, r = Math.min(e, n, 1); r < 1 && (i = Math.round(i * r), a = Math.round(a * r)); } let o = new OffscreenCanvas(i, a), s = o.getContext("2d"); if (!s) throw r.close(), Error("OffscreenCanvas 2D context unavailable"); s.drawImage(r, 0, 0, i, a), r.close(); let c = t.format === "webp" ? "image/webp" : t.format === "jpeg" ? "image/jpeg" : "image/png", l = await o.convertToBlob({ type: c, quality: t.quality / 100 }); return new Uint8Array(await l.arrayBuffer()); } function h() { return function(e) { let t = e instanceof Uint8Array ? e : new Uint8Array(e), n = "webp", r = 80, i, a, o = { resize(e, t, n) { return i = e, a = t, o; }, webp(e) { return n = "webp", typeof e?.quality == "number" && (r = e.quality), o; }, jpeg(e) { return n = "jpeg", typeof e?.quality == "number" && (r = e.quality), o; }, png(e) { return n = "png", o; }, toFormat(e, t) { return (e === "webp" || e === "jpeg" || e === "png") && (n = e), typeof t?.quality == "number" && (r = t.quality), o; }, async toBuffer() { return m(t, { format: n, quality: r, maxWidth: i, maxHeight: a }); }, async metadata() { let e = new Blob([t]), n = await createImageBitmap(e), r = { width: n.width, height: n.height, format: "unknown" }; return n.close(), r; } }; return o; }; } //#endregion //#region packages/hooks/src/use-optimize-model/utils/texture-optimization.ts var g = async (e, t) => { e.hasModel() && await e.compressTextures({ ...t, encoder: h() }); }, _ = () => { let [a, c] = o(d, u), { report: f, error: m, loading: h } = a, { info: _, reset: v } = p(a), y = s(new l()); i(() => { let e = y.current; return () => { e.reset(); }; }, []); let b = r(async (e) => { c({ type: "LOAD_START" }); try { let t = y.current; await t.loadFromThreeJS(e), c({ type: "LOAD_SUCCESS", payload: { report: await t.getReport() } }); } catch (e) { c({ type: "LOAD_ERROR", payload: e }), console.error("Error loading model:", e); } }, []), x = r(async (r) => { c({ type: "LOAD_START" }); try { let i = y.current, a = /* @__PURE__ */ new Map(); for (let t of Object.values(r.assetData ?? {})) for (let n of e(t.fileName)) a.set(n, t); let o = {}, s = r.gltfJson, l = [...(s.images ?? []).map((e) => e.uri).filter((e) => !!e && !e.startsWith("data:")), ...(s.buffers ?? []).map((e) => e.uri).filter((e) => !!e && !e.startsWith("data:"))]; for (let e of l) { let r = t(e), i = r.split("/").pop() || r, s = a.get(e) ?? a.get(r) ?? a.get(i); s && (o[e] = n(s)); } let u = { json: r.gltfJson, resources: o }; await i.loadFromJSON(u), c({ type: "LOAD_SUCCESS", payload: { report: await i.getReport() } }); } catch (e) { throw c({ type: "LOAD_ERROR", payload: e }), console.error("Error loading server scene into optimizer:", e), e; } }, []), S = r(async (e, t) => { c({ type: "LOAD_START" }); try { await y.current.loadFromBuffer(e), y.current.setAppliedOptimizations(t?.appliedOptimizations ?? []), y.current.setDracoReport(t?.dracoReport ?? null), c({ type: "LOAD_SUCCESS", payload: { report: await y.current.getReport() } }); } catch (e) { throw c({ type: "LOAD_ERROR", payload: e }), console.error("Error loading GLB buffer into optimizer:", e), e; } }, []), C = r(async (e, t) => { c({ type: "LOAD_START" }); try { await y.current.loadFromGLTFWithAssets(e, t), c({ type: "LOAD_SUCCESS", payload: { report: await y.current.getReport() } }); } catch (e) { throw c({ type: "LOAD_ERROR", payload: e }), console.error("Error loading GLTF with assets into optimizer:", e), e; } }, []), w = r(async (e) => { if (y.current.hasModel()) try { await y.current.simplify(e), c({ type: "LOAD_SUCCESS", payload: { report: await y.current.getReport() } }); } catch (e) { console.error("Simplification failed:", e); } }, []), T = r(async (e) => { if (y.current.hasModel()) try { await y.current.deduplicate(e), c({ type: "LOAD_SUCCESS", payload: { report: await y.current.getReport() } }); } catch (e) { console.error("Deduplication failed:", e); } }, []), E = r(async (e) => { if (y.current.hasModel()) try { await y.current.quantize(e), c({ type: "LOAD_SUCCESS", payload: { report: await y.current.getReport() } }); } catch (e) { console.error("Quantization failed:", e); } }, []), D = r(async (e) => { if (y.current.hasModel()) try { await y.current.optimizeNormals(e), c({ type: "LOAD_SUCCESS", payload: { report: await y.current.getReport() } }); } catch (e) { console.error("Normals optimization failed:", e); } }, []), O = r(async (e) => { try { await g(y.current, e), c({ type: "LOAD_SUCCESS", payload: { report: await y.current.getReport() } }); } catch (e) { throw console.error("Texture optimization failed:", e), e; } }, []), k = r(async () => { if (!y.current.hasModel()) return null; try { return await y.current.export(); } catch (e) { return console.error("Error getting model binary:", e), null; } }, []), A = r(() => y.current.hasModel() ? y.current.document : null, []); return { load: b, loadFromServerSceneData: x, loadFromGlbBuffer: S, loadFromGLTFWithAssets: C, getModel: k, isReady: !!A() && !h, _getDocument: A, reset: r(() => { y.current.reset(), c({ type: "RESET" }), v(); }, [v]), error: m, loading: h, report: f, info: _, simplifyOptimization: w, dedupOptimization: T, quantizeOptimization: E, normalsOptimization: D, texturesOptimization: O }; }; //#endregion export { _ as t };