@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
JavaScript
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 };