@vctrl/hooks 0.17.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +74 -0
- package/README.md +9 -9
- package/chunk-CMqjfN_6.cjs +1 -0
- package/index.cjs.js +1 -1
- package/index.es.js +3 -3
- package/package.json +67 -67
- package/use-export-model-8JbUalyf.cjs +1 -0
- package/{use-export-model-DzVKdqnY.js → use-export-model-BXg6L_W5.js} +5 -5
- package/use-export-model.cjs.js +1 -1
- package/use-export-model.es.js +1 -1
- package/use-load-model/error-helpers.d.ts +5 -0
- package/use-load-model/file-loaders.d.ts +12 -0
- package/use-load-model/index.d.ts +1 -1
- package/use-load-model/optimizer-integration.d.ts +16 -0
- package/use-load-model/scene-data-builder.d.ts +3 -0
- package/use-load-model/types.d.ts +13 -4
- package/use-load-model/use-load-model.d.ts +5 -16
- package/use-load-model-B9M4cF4B.cjs +1 -0
- package/use-load-model-BDCy9n2U.js +504 -0
- package/use-load-model.cjs.js +1 -1
- package/use-load-model.es.js +1 -1
- package/use-optimize-model/use-optimize-model.d.ts +14 -12
- package/use-optimize-model/utils/browser-texture-encoder.d.ts +41 -0
- package/use-optimize-model/utils/index.d.ts +2 -2
- package/use-optimize-model/utils/texture-optimization.d.ts +3 -13
- package/use-optimize-model-CPGezwrM.cjs +1 -0
- package/use-optimize-model-KAGxtv_k.js +296 -0
- package/use-optimize-model.cjs.js +1 -1
- package/use-optimize-model.es.js +1 -1
- package/GLTFLoader-CygFefRr.cjs +0 -1
- package/GLTFLoader-NiJ7qzh1.js +0 -1381
- package/__vite-browser-external-Bn_csQuM.js +0 -7
- package/__vite-browser-external-CVmj8TrW.cjs +0 -1
- package/chunk-foFoljWS.js +0 -23
- package/chunk-gGpyby2o.cjs +0 -1
- package/lib-DKn2MJ9X.js +0 -4111
- package/lib-DZ-MihdJ.cjs +0 -2
- package/model-exporter-SMSAY90N.cjs +0 -1
- package/model-exporter-cvguDGnn.js +0 -6512
- package/server-communication-Bj6ZxFw2.js +0 -4659
- package/server-communication-yXZR8eso.cjs +0 -18
- package/use-export-model-CaijtW1c.cjs +0 -1
- package/use-load-model-B6UXiFWh.js +0 -517
- package/use-load-model-CH-WPQ_s.cjs +0 -1
- package/use-optimize-model/utils/validation.d.ts +0 -8
- package/use-optimize-model-C-fzzKh6.cjs +0 -1
- package/use-optimize-model-Cb3WMvDZ.js +0 -299
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
import { ModelFileTypes as e, ModelLoader as t } from "@vctrl/core/model-loader";
|
|
2
|
+
import { useCallback as n, useMemo as r, useReducer as i, useRef as a } from "react";
|
|
3
|
+
import { PERSISTED_BAKE_FILENAME as o, buildAssetLookupKeys as s, getSerializedAssetByteSize as c, isValidBase64 as l, normalizeAssetUri as u, normalizeCameraSettings as d, normalizeSceneInteractions as f, toSerializedAssetBytes as p } from "@vctrl/core";
|
|
4
|
+
//#region packages/hooks/src/use-load-model/error-helpers.ts
|
|
5
|
+
var m = ({ code: e, message: t, recoverable: n, source: r, cause: i, context: a }) => ({
|
|
6
|
+
code: e,
|
|
7
|
+
message: t,
|
|
8
|
+
recoverable: n,
|
|
9
|
+
source: r,
|
|
10
|
+
cause: i,
|
|
11
|
+
context: a
|
|
12
|
+
}), h = (e) => e instanceof Error && e.message ? e.message : typeof e == "string" && e.trim().length > 0 ? e : "Unknown error", g = (e, t, n) => {
|
|
13
|
+
let r = h(e);
|
|
14
|
+
return m({
|
|
15
|
+
code: r.includes("missing required referenced assets") ? "missing_assets" : t,
|
|
16
|
+
message: r,
|
|
17
|
+
recoverable: !0,
|
|
18
|
+
source: "local-upload",
|
|
19
|
+
cause: e,
|
|
20
|
+
context: n
|
|
21
|
+
});
|
|
22
|
+
}, _ = (e, t) => {
|
|
23
|
+
let n = h(e), r = "server_load_failed";
|
|
24
|
+
return n.includes("Server responded with 404") ? r = "not_found" : (n.includes("Server responded with 402") || n.includes("Server responded with 403")) && (r = "quota_exceeded"), m({
|
|
25
|
+
code: r,
|
|
26
|
+
message: n,
|
|
27
|
+
recoverable: r !== "not_found",
|
|
28
|
+
source: "server-load",
|
|
29
|
+
cause: e,
|
|
30
|
+
context: { sceneId: t }
|
|
31
|
+
});
|
|
32
|
+
}, v = {}, y = {
|
|
33
|
+
emit(e, t) {
|
|
34
|
+
let n = v[e];
|
|
35
|
+
console.debug(`Event emitted: ${e}`, t), n && n.forEach((e) => e(t));
|
|
36
|
+
},
|
|
37
|
+
on(e, t) {
|
|
38
|
+
v[e] || (v[e] = []), v[e].push(t);
|
|
39
|
+
},
|
|
40
|
+
off(e, t) {
|
|
41
|
+
let n = v[e];
|
|
42
|
+
n && (v[e] = n.filter((e) => e !== t));
|
|
43
|
+
}
|
|
44
|
+
}, b = (e) => {
|
|
45
|
+
try {
|
|
46
|
+
return u(e);
|
|
47
|
+
} catch {
|
|
48
|
+
return e;
|
|
49
|
+
}
|
|
50
|
+
}, x = (e) => {
|
|
51
|
+
let t = /* @__PURE__ */ new Set(), n = e, r = Array.isArray(n.images) ? n.images : [];
|
|
52
|
+
for (let e of r) typeof e.uri == "string" && !e.uri.startsWith("data:") && t.add(e.uri);
|
|
53
|
+
let i = Array.isArray(n.buffers) ? n.buffers : [];
|
|
54
|
+
for (let e of i) typeof e.uri == "string" && !e.uri.startsWith("data:") && t.add(e.uri);
|
|
55
|
+
return t;
|
|
56
|
+
}, S = async (e, t) => {
|
|
57
|
+
let n = JSON.parse(await e.text()), r = x(n), i = {}, a = /* @__PURE__ */ new Map(), o = /* @__PURE__ */ new Map(), c = /* @__PURE__ */ new Set(), l = async (e) => {
|
|
58
|
+
let t = o.get(e);
|
|
59
|
+
if (t) return t;
|
|
60
|
+
let n = new Uint8Array(await e.arrayBuffer());
|
|
61
|
+
return o.set(e, n), n;
|
|
62
|
+
};
|
|
63
|
+
for (let e of t) {
|
|
64
|
+
for (let t of s(e.name)) a.set(t, e);
|
|
65
|
+
if (e.webkitRelativePath) for (let t of s(e.webkitRelativePath)) a.set(t, e);
|
|
66
|
+
}
|
|
67
|
+
let u = [];
|
|
68
|
+
for (let e of r) {
|
|
69
|
+
let t = b(e), n = t.split("/").pop() || t, r = a.get(e) || a.get(t) || a.get(n);
|
|
70
|
+
if (!r) {
|
|
71
|
+
u.push(e);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
c.add(r);
|
|
75
|
+
let o = await l(r);
|
|
76
|
+
i[t] = {
|
|
77
|
+
data: Array.from(o),
|
|
78
|
+
fileName: t,
|
|
79
|
+
mimeType: r.type || "application/octet-stream"
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (u.length > 0) throw Error(`Scene payload is missing required referenced assets: ${u.slice(0, 5).join(", ")}`);
|
|
83
|
+
for (let [e, n] of t.entries()) {
|
|
84
|
+
if (c.has(n)) continue;
|
|
85
|
+
let t = b(n.name), r = t.split("/").pop() || t, a = await l(n);
|
|
86
|
+
i[`extra-${e}-${r}`] = {
|
|
87
|
+
data: Array.from(a),
|
|
88
|
+
fileName: r,
|
|
89
|
+
mimeType: n.type || "application/octet-stream"
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
return {
|
|
93
|
+
gltfJson: n,
|
|
94
|
+
assetData: i
|
|
95
|
+
};
|
|
96
|
+
}, C = async (t, n, r, i, a) => {
|
|
97
|
+
try {
|
|
98
|
+
let o = {
|
|
99
|
+
model: (await r.loadToThreeJS(t)).scene,
|
|
100
|
+
type: n,
|
|
101
|
+
name: t.name
|
|
102
|
+
};
|
|
103
|
+
if (a({
|
|
104
|
+
type: "set-file",
|
|
105
|
+
payload: o
|
|
106
|
+
}), a({
|
|
107
|
+
type: "set-file-loading",
|
|
108
|
+
payload: !1
|
|
109
|
+
}), i && n !== e.usdz) {
|
|
110
|
+
let e = new Uint8Array(await t.arrayBuffer());
|
|
111
|
+
await i.loadFromGlbBuffer(e);
|
|
112
|
+
}
|
|
113
|
+
return o;
|
|
114
|
+
} catch (e) {
|
|
115
|
+
return console.error("Error loading binary model:", e), a({
|
|
116
|
+
type: "set-file-loading",
|
|
117
|
+
payload: !1
|
|
118
|
+
}), y.emit("load-error", g(e, "binary_load_failed", {
|
|
119
|
+
fileName: t.name,
|
|
120
|
+
fileType: n,
|
|
121
|
+
fileSize: t.size
|
|
122
|
+
})), null;
|
|
123
|
+
}
|
|
124
|
+
}, w = async (t, n, r, i, a, o) => {
|
|
125
|
+
try {
|
|
126
|
+
let { sourcePackageBytes: s, textureBytes: c } = await o(t, n), l = await r.loadGLTFWithAssetsToThreeJS(t, n), u = {
|
|
127
|
+
model: l.scene,
|
|
128
|
+
type: e.gltf,
|
|
129
|
+
name: t.name,
|
|
130
|
+
sourcePackageBytes: s,
|
|
131
|
+
sourceTextureBytes: c
|
|
132
|
+
};
|
|
133
|
+
if (a({
|
|
134
|
+
type: "set-file",
|
|
135
|
+
payload: u
|
|
136
|
+
}), a({
|
|
137
|
+
type: "set-file-loading",
|
|
138
|
+
payload: !1
|
|
139
|
+
}), i) try {
|
|
140
|
+
let e = await S(t, n);
|
|
141
|
+
await i.loadFromServerSceneData(e);
|
|
142
|
+
} catch (e) {
|
|
143
|
+
console.warn("Failed to initialize optimizer from source GLTF payload; trying direct asset load.", e);
|
|
144
|
+
try {
|
|
145
|
+
if ("loadFromGLTFWithAssets" in i) {
|
|
146
|
+
let e = new Uint8Array(await t.arrayBuffer()), r = /* @__PURE__ */ new Map();
|
|
147
|
+
for (let e of n) {
|
|
148
|
+
let t = new Uint8Array(await e.arrayBuffer());
|
|
149
|
+
r.set(e.name, t), e.webkitRelativePath && r.set(e.webkitRelativePath, t);
|
|
150
|
+
}
|
|
151
|
+
await i.loadFromGLTFWithAssets(e, r);
|
|
152
|
+
} else throw Error("loadFromGLTFWithAssets not available");
|
|
153
|
+
} catch (e) {
|
|
154
|
+
console.warn("Direct asset load also failed; falling back to Three.js scene import. Texture names may be generic.", e), await i.load(l.scene);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
return u;
|
|
158
|
+
} catch (e) {
|
|
159
|
+
return console.error("Error loading GLTF model:", e), console.error("GLTF file:", t.name), console.error("Asset files:", n.map((e) => e.name)), a({
|
|
160
|
+
type: "set-file-loading",
|
|
161
|
+
payload: !1
|
|
162
|
+
}), y.emit("load-error", g(e, "gltf_load_failed", {
|
|
163
|
+
fileName: t.name,
|
|
164
|
+
fileSize: t.size,
|
|
165
|
+
assetCount: n.length
|
|
166
|
+
})), null;
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
//#endregion
|
|
170
|
+
//#region packages/hooks/src/use-load-model/optimizer-integration.ts
|
|
171
|
+
function T(t, r, i, a) {
|
|
172
|
+
let o = n(async (n, o) => {
|
|
173
|
+
if (!t) {
|
|
174
|
+
console.warn("Optimizer is not available");
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
n && await n(o);
|
|
179
|
+
let s = await t.getModel();
|
|
180
|
+
if (!s) {
|
|
181
|
+
console.warn("No optimized model available after optimization");
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
let c = s instanceof Uint8Array ? s.slice() : new Uint8Array(s).slice(), l = new File([c], i?.name || "optimized_model.glb", { type: "model/gltf-binary" });
|
|
185
|
+
r({
|
|
186
|
+
type: "set-file",
|
|
187
|
+
payload: {
|
|
188
|
+
model: (await a.loadToThreeJS(l)).scene,
|
|
189
|
+
type: e.glb,
|
|
190
|
+
name: l.name
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
} catch (e) {
|
|
194
|
+
console.error("Optimization failed:", e);
|
|
195
|
+
}
|
|
196
|
+
}, [
|
|
197
|
+
t,
|
|
198
|
+
a,
|
|
199
|
+
i,
|
|
200
|
+
r
|
|
201
|
+
]);
|
|
202
|
+
if (!t) return null;
|
|
203
|
+
let s = t.isReady, c = !!i?.model && !s;
|
|
204
|
+
return {
|
|
205
|
+
...t,
|
|
206
|
+
isReady: s,
|
|
207
|
+
isPreparing: c,
|
|
208
|
+
applyOptimization: o,
|
|
209
|
+
reset: t?.reset,
|
|
210
|
+
error: t?.error,
|
|
211
|
+
loading: t?.loading
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
//#endregion
|
|
215
|
+
//#region packages/hooks/src/use-load-model/state.ts
|
|
216
|
+
var E = {
|
|
217
|
+
file: null,
|
|
218
|
+
isFileLoading: !1,
|
|
219
|
+
progress: 0,
|
|
220
|
+
supportedFileTypes: [
|
|
221
|
+
e.gltf,
|
|
222
|
+
e.glb,
|
|
223
|
+
e.usdz
|
|
224
|
+
]
|
|
225
|
+
};
|
|
226
|
+
function D(e, t) {
|
|
227
|
+
switch (t.type) {
|
|
228
|
+
case "set-file": return {
|
|
229
|
+
...e,
|
|
230
|
+
file: t.payload
|
|
231
|
+
};
|
|
232
|
+
case "set-file-loading": return {
|
|
233
|
+
...e,
|
|
234
|
+
isFileLoading: t.payload
|
|
235
|
+
};
|
|
236
|
+
case "set-progress": return {
|
|
237
|
+
...e,
|
|
238
|
+
progress: t.payload
|
|
239
|
+
};
|
|
240
|
+
case "reset-state": return { ...E };
|
|
241
|
+
default: return e;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
//#endregion
|
|
245
|
+
//#region packages/hooks/src/use-load-model/utils/read-directory.ts
|
|
246
|
+
async function O(e) {
|
|
247
|
+
let t = [];
|
|
248
|
+
async function* n(e) {
|
|
249
|
+
let t = e;
|
|
250
|
+
for await (let [, e] of t) e.kind === "file" ? yield await e.getFile() : e.kind === "directory" && (yield* n(e));
|
|
251
|
+
}
|
|
252
|
+
for await (let r of n(e)) t.push(r);
|
|
253
|
+
return t;
|
|
254
|
+
}
|
|
255
|
+
//#endregion
|
|
256
|
+
//#region packages/hooks/src/use-load-model/utils/reconstruct-files.ts
|
|
257
|
+
function k(e) {
|
|
258
|
+
let t = [];
|
|
259
|
+
if (e.assetData && typeof e.assetData == "object") for (let [, n] of Object.entries(e.assetData)) {
|
|
260
|
+
let { fileName: e, mimeType: r } = n;
|
|
261
|
+
if (e === o) continue;
|
|
262
|
+
let i = p(n), a = new Uint8Array(i.byteLength);
|
|
263
|
+
a.set(i);
|
|
264
|
+
let s = new Blob([a], { type: r }), c = new File([s], e, { type: r });
|
|
265
|
+
t.push(c);
|
|
266
|
+
}
|
|
267
|
+
let n = JSON.stringify(e.gltfJson), r = new Blob([n], { type: "model/gltf+json" }), i = e.meta?.name, a = i ? `${i}.gltf` : "scene.gltf";
|
|
268
|
+
return [new File([r], a, { type: "model/gltf+json" }), ...t];
|
|
269
|
+
}
|
|
270
|
+
//#endregion
|
|
271
|
+
//#region packages/hooks/src/use-load-model/utils/calculate-referenced-bytes.ts
|
|
272
|
+
var A = (e, t) => {
|
|
273
|
+
let n = u(e), r = n.split("/").pop() || n;
|
|
274
|
+
for (let e of t) {
|
|
275
|
+
let t = s(e.fileName);
|
|
276
|
+
if (t.has(n) || t.has(r)) return e.size;
|
|
277
|
+
}
|
|
278
|
+
return 0;
|
|
279
|
+
}, j = (e) => {
|
|
280
|
+
let t = /* @__PURE__ */ new Set(), n = /* @__PURE__ */ new Set(), r = e, i = Array.isArray(r.images) ? r.images : [];
|
|
281
|
+
for (let e of i) typeof e.uri == "string" && !e.uri.startsWith("data:") && n.add(e.uri);
|
|
282
|
+
let a = Array.isArray(r.buffers) ? r.buffers : [];
|
|
283
|
+
for (let e of a) typeof e.uri == "string" && !e.uri.startsWith("data:") && t.add(e.uri);
|
|
284
|
+
return {
|
|
285
|
+
bufferUris: t,
|
|
286
|
+
imageUris: n
|
|
287
|
+
};
|
|
288
|
+
};
|
|
289
|
+
async function M(e, t) {
|
|
290
|
+
let n = await e.text(), r;
|
|
291
|
+
try {
|
|
292
|
+
r = JSON.parse(n);
|
|
293
|
+
} catch {
|
|
294
|
+
return {
|
|
295
|
+
sourcePackageBytes: e.size,
|
|
296
|
+
textureBytes: 0
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
let i = t.map((e) => ({
|
|
300
|
+
fileName: e.name,
|
|
301
|
+
size: e.size
|
|
302
|
+
})), { bufferUris: a, imageUris: o } = j(r), s = Array.from(a).reduce((e, t) => e + A(t, i), 0), c = Array.from(o).reduce((e, t) => e + A(t, i), 0);
|
|
303
|
+
return {
|
|
304
|
+
sourcePackageBytes: e.size + s + c,
|
|
305
|
+
textureBytes: c
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
function N(e) {
|
|
309
|
+
let t = e.gltfJson, n = Object.values(e.assetData || {}).map((e) => ({
|
|
310
|
+
fileName: e.fileName,
|
|
311
|
+
size: c(e.data)
|
|
312
|
+
})), r = new TextEncoder().encode(JSON.stringify(t)).byteLength, { bufferUris: i, imageUris: a } = j(t), o = Array.from(i).reduce((e, t) => e + A(t, n), 0), s = Array.from(a).reduce((e, t) => e + A(t, n), 0);
|
|
313
|
+
return {
|
|
314
|
+
sourcePackageBytes: r + o + s,
|
|
315
|
+
textureBytes: s
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
//#endregion
|
|
319
|
+
//#region packages/hooks/src/use-load-model/utils/resolve-scene-payload.ts
|
|
320
|
+
function P(e) {
|
|
321
|
+
return typeof e == "object" && !!e && !Array.isArray(e);
|
|
322
|
+
}
|
|
323
|
+
function F(e) {
|
|
324
|
+
let t = P(e.settings) ? e.settings : {}, n = d(e.camera ?? t.camera);
|
|
325
|
+
return {
|
|
326
|
+
...t,
|
|
327
|
+
bounds: e.bounds ?? t.bounds,
|
|
328
|
+
camera: n,
|
|
329
|
+
interactions: f(t.interactions, { camera: n }),
|
|
330
|
+
controls: e.controls ?? t.controls,
|
|
331
|
+
environment: e.environment ?? t.environment,
|
|
332
|
+
shadows: e.shadows ?? t.shadows
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
function I(e) {
|
|
336
|
+
let t = /* @__PURE__ */ new Set(), n = e, r = Array.isArray(n.images) ? n.images : [];
|
|
337
|
+
for (let e of r) typeof e.uri == "string" && !e.uri.startsWith("data:") && t.add(e.uri);
|
|
338
|
+
let i = Array.isArray(n.buffers) ? n.buffers : [];
|
|
339
|
+
for (let e of i) typeof e.uri == "string" && !e.uri.startsWith("data:") && t.add(e.uri);
|
|
340
|
+
return t;
|
|
341
|
+
}
|
|
342
|
+
function L(e) {
|
|
343
|
+
for (let [t, n] of Object.entries(e)) {
|
|
344
|
+
if (!n || typeof n != "object") throw Error(`Scene payload contains invalid asset entry for ${t}`);
|
|
345
|
+
if (typeof n.fileName != "string" || n.fileName.trim().length === 0) throw Error(`Scene payload asset ${t} is missing a valid fileName`);
|
|
346
|
+
if (typeof n.mimeType != "string" || n.mimeType.trim().length === 0) throw Error(`Scene payload asset ${t} is missing a valid mimeType`);
|
|
347
|
+
if (typeof n.data == "string" && n.encoding === "base64" && !l(n.data)) throw Error(`Scene payload asset ${n.fileName} has invalid base64 data`);
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
function R(e, t) {
|
|
351
|
+
let n = I(e);
|
|
352
|
+
if (n.size === 0) return;
|
|
353
|
+
let r = /* @__PURE__ */ new Set();
|
|
354
|
+
for (let e of Object.values(t)) for (let t of s(e.fileName)) r.add(t);
|
|
355
|
+
let i = [];
|
|
356
|
+
for (let e of n) {
|
|
357
|
+
let t = u(e), n = t.split("/").pop() || t;
|
|
358
|
+
!r.has(e) && !r.has(t) && !r.has(n) && i.push(e);
|
|
359
|
+
}
|
|
360
|
+
if (i.length > 0) throw Error(`Scene payload is missing required referenced assets: ${i.slice(0, 5).join(", ")}`);
|
|
361
|
+
}
|
|
362
|
+
function z(e) {
|
|
363
|
+
if (!P(e.gltfJson)) throw Error("Scene payload is missing a valid glTF document");
|
|
364
|
+
let t = e.assetData ?? {};
|
|
365
|
+
return L(t), R(e.gltfJson, t), {
|
|
366
|
+
meta: e.meta,
|
|
367
|
+
gltfJson: e.gltfJson,
|
|
368
|
+
assetData: t,
|
|
369
|
+
...F(e)
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
//#endregion
|
|
373
|
+
//#region packages/hooks/src/use-load-model/use-load-model.ts
|
|
374
|
+
function B(o) {
|
|
375
|
+
let s = a(!1), c = a(null), [l, u] = i(D, E), d = r(() => {
|
|
376
|
+
let e = new t();
|
|
377
|
+
return e.onProgress((e) => {
|
|
378
|
+
u({
|
|
379
|
+
type: "set-progress",
|
|
380
|
+
payload: e.progress
|
|
381
|
+
}), e.progress !== 100 && y.emit("load-progress", e.progress);
|
|
382
|
+
}), e;
|
|
383
|
+
}, []), f = n((e) => {
|
|
384
|
+
u({
|
|
385
|
+
type: "set-progress",
|
|
386
|
+
payload: e
|
|
387
|
+
}), y.emit("load-progress", e);
|
|
388
|
+
}, []), p = n(() => {
|
|
389
|
+
u({ type: "reset-state" }), y.emit("load-reset");
|
|
390
|
+
}, []), m = n((e, t) => e.find((e) => e.name.endsWith("." + t)), []), h = n(async (t) => {
|
|
391
|
+
if (t.length === 0) return;
|
|
392
|
+
let n = m(t, e.gltf), r = m(t, e.glb), i = m(t, e.usdz), a = [
|
|
393
|
+
n,
|
|
394
|
+
r,
|
|
395
|
+
i
|
|
396
|
+
].filter(Boolean);
|
|
397
|
+
if (a.length > 1) {
|
|
398
|
+
y.emit("multiple-models", a);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
let l = t.filter((e) => e !== n && e !== r && e !== i), f = () => {
|
|
402
|
+
s.current = !0, c.current && (y.emit("load-complete", c.current), s.current = !1);
|
|
403
|
+
};
|
|
404
|
+
if (n) {
|
|
405
|
+
let e = await w(n, l, d, o, u, M);
|
|
406
|
+
e && (c.current = e), f();
|
|
407
|
+
} else if (r) {
|
|
408
|
+
let t = await C(r, e.glb, d, o, u);
|
|
409
|
+
t && (c.current = t), f();
|
|
410
|
+
} else if (i) {
|
|
411
|
+
let t = await C(i, e.usdz, d, o, u);
|
|
412
|
+
t && (c.current = t), f();
|
|
413
|
+
} else y.emit("not-loaded-files", t), u({
|
|
414
|
+
type: "set-file-loading",
|
|
415
|
+
payload: !1
|
|
416
|
+
});
|
|
417
|
+
}, [
|
|
418
|
+
m,
|
|
419
|
+
d,
|
|
420
|
+
o
|
|
421
|
+
]), g = n(async (e) => {
|
|
422
|
+
let t = [];
|
|
423
|
+
y.emit("load-start"), u({ type: "reset-state" }), u({
|
|
424
|
+
type: "set-file-loading",
|
|
425
|
+
payload: !0
|
|
426
|
+
}), f(0), s.current = !1;
|
|
427
|
+
for (let n of e) if (n instanceof File) t.push(n);
|
|
428
|
+
else if ("kind" in n && n.kind === "directory") {
|
|
429
|
+
let e = await O(n);
|
|
430
|
+
t.push(...e);
|
|
431
|
+
}
|
|
432
|
+
await h(t);
|
|
433
|
+
}, [h, f]), v = n(async (t) => {
|
|
434
|
+
let { sceneData: n, sceneId: r } = t;
|
|
435
|
+
f(40);
|
|
436
|
+
let i = k(n), { sourcePackageBytes: a, textureBytes: s } = N(n);
|
|
437
|
+
f(60);
|
|
438
|
+
let l = i[0], p = i.slice(1), m = {
|
|
439
|
+
model: (await d.loadGLTFWithAssetsToThreeJS(l, p)).scene,
|
|
440
|
+
type: e.gltf,
|
|
441
|
+
name: l.name,
|
|
442
|
+
sourcePackageBytes: a,
|
|
443
|
+
sourceTextureBytes: s
|
|
444
|
+
};
|
|
445
|
+
return u({
|
|
446
|
+
type: "set-file",
|
|
447
|
+
payload: m
|
|
448
|
+
}), u({
|
|
449
|
+
type: "set-file-loading",
|
|
450
|
+
payload: !1
|
|
451
|
+
}), c.current = m, o && await o.loadFromServerSceneData(n), f(100), {
|
|
452
|
+
file: m,
|
|
453
|
+
sceneId: r,
|
|
454
|
+
...n
|
|
455
|
+
};
|
|
456
|
+
}, [
|
|
457
|
+
d,
|
|
458
|
+
o,
|
|
459
|
+
f
|
|
460
|
+
]), b = n(async (e) => {
|
|
461
|
+
let { sceneId: t, serverOptions: n } = e;
|
|
462
|
+
try {
|
|
463
|
+
y.emit("server-load-start", t), u({ type: "reset-state" }), u({
|
|
464
|
+
type: "set-file-loading",
|
|
465
|
+
payload: !0
|
|
466
|
+
}), f(0);
|
|
467
|
+
let e = new FormData();
|
|
468
|
+
e.append("action", "get-scene-settings"), e.append("sceneId", t);
|
|
469
|
+
let r = n?.endpoint ?? `/api/scenes/${t}`, i = n?.apiKey ? {
|
|
470
|
+
Authorization: `Bearer ${n.apiKey}`,
|
|
471
|
+
...n.headers
|
|
472
|
+
} : { ...n?.headers }, a = await fetch(r, {
|
|
473
|
+
method: "POST",
|
|
474
|
+
headers: i,
|
|
475
|
+
body: e
|
|
476
|
+
});
|
|
477
|
+
if (!a.ok) throw Error(`Server responded with ${a.status} ${a.statusText}`);
|
|
478
|
+
let o = await a.json(), s = await v({
|
|
479
|
+
sceneId: t,
|
|
480
|
+
sceneData: z(o.data ?? o)
|
|
481
|
+
});
|
|
482
|
+
return y.emit("server-load-complete", s), s;
|
|
483
|
+
} catch (e) {
|
|
484
|
+
console.error("Server scene loading failed:", e), u({
|
|
485
|
+
type: "set-file-loading",
|
|
486
|
+
payload: !1
|
|
487
|
+
});
|
|
488
|
+
let n = _(e, t);
|
|
489
|
+
throw y.emit("server-load-error", n), n;
|
|
490
|
+
}
|
|
491
|
+
}, [v, f]), x = T(o, u, l.file, d);
|
|
492
|
+
return {
|
|
493
|
+
...l,
|
|
494
|
+
on: y.on,
|
|
495
|
+
off: y.off,
|
|
496
|
+
load: g,
|
|
497
|
+
loadFromData: v,
|
|
498
|
+
loadFromServer: b,
|
|
499
|
+
reset: p,
|
|
500
|
+
optimizer: x
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
//#endregion
|
|
504
|
+
export { k as n, B as t };
|
package/use-load-model.cjs.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require(
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./chunk-CMqjfN_6.cjs"),t=require("./use-load-model-B9M4cF4B.cjs");let n=require("react");n=e.t(n,1);let r=require("react/jsx-runtime");var i=(0,n.createContext)(void 0),a=(0,n.createContext)(void 0),o=({children:e,optimizer:n})=>{let o=t.t(n);return n?(0,r.jsx)(i.Provider,{value:o,children:e}):(0,r.jsx)(a.Provider,{value:o,children:e})};function s(e){let t=(0,n.useContext)(i),r=(0,n.useContext)(a),o=t??r;if(!o)throw Error(`useModelContext must be used within a ModelProvider`);return o}exports.ModelProvider=o,exports.reconstructGltfFiles=t.n,exports.useLoadModel=t.t,exports.useModelContext=s;
|
package/use-load-model.es.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { n as e, t } from "./use-load-model-
|
|
1
|
+
import { n as e, t } from "./use-load-model-BDCy9n2U.js";
|
|
2
2
|
import { createContext as n, useContext as r } from "react";
|
|
3
3
|
import { jsx as i } from "react/jsx-runtime";
|
|
4
4
|
//#region packages/hooks/src/use-load-model/model-context.tsx
|
|
@@ -13,22 +13,22 @@ import { ServerSceneData } from '../../../core/src/index.ts';
|
|
|
13
13
|
* - Geometry deduplication to remove redundant data
|
|
14
14
|
* - Vertex attribute quantization to reduce file size
|
|
15
15
|
* - Normal vector optimization
|
|
16
|
-
* - Texture compression (
|
|
16
|
+
* - Texture compression (browser-native via OffscreenCanvas)
|
|
17
17
|
* - Progress tracking and error handling
|
|
18
18
|
* - Optimization reports with before/after metrics
|
|
19
19
|
*
|
|
20
20
|
* @example
|
|
21
21
|
* const optimizer = useOptimizeModel()
|
|
22
22
|
*
|
|
23
|
-
* // Load a
|
|
24
|
-
* await optimizer.
|
|
23
|
+
* // Load a GLB into the optimizer
|
|
24
|
+
* await optimizer.loadFromGlbBuffer(glbBytes)
|
|
25
25
|
*
|
|
26
|
-
* //
|
|
27
|
-
*
|
|
28
|
-
* await optimizer.
|
|
26
|
+
* // Geometry optimizations run via Web Worker (use-optimization-process.ts)
|
|
27
|
+
* // Texture compression runs browser-native via texturesOptimization()
|
|
28
|
+
* await optimizer.texturesOptimization({ targetFormat: 'webp', quality: 0.8 })
|
|
29
29
|
*
|
|
30
|
-
* //
|
|
31
|
-
*
|
|
30
|
+
* // Sync result back to the Three.js viewer
|
|
31
|
+
* await optimizer.applyOptimization()
|
|
32
32
|
*
|
|
33
33
|
* @returns Object containing optimization methods, state, and report data
|
|
34
34
|
*/
|
|
@@ -42,6 +42,8 @@ declare const useOptimizeModel: () => {
|
|
|
42
42
|
*/
|
|
43
43
|
load: (model: Object3D) => Promise<void>;
|
|
44
44
|
loadFromServerSceneData: (sceneData: ServerSceneData) => Promise<void>;
|
|
45
|
+
loadFromGlbBuffer: (buffer: Uint8Array) => Promise<void>;
|
|
46
|
+
loadFromGLTFWithAssets: (gltfBytes: Uint8Array, assets: Map<string, Uint8Array>) => Promise<void>;
|
|
45
47
|
/**
|
|
46
48
|
* Retrieves the current model as a binary Uint8Array in glTF (.glb) format.
|
|
47
49
|
*
|
|
@@ -107,12 +109,12 @@ declare const useOptimizeModel: () => {
|
|
|
107
109
|
*/
|
|
108
110
|
normalsOptimization: (options?: NormalsOptions) => Promise<void>;
|
|
109
111
|
/**
|
|
110
|
-
* Compresses textures using
|
|
111
|
-
*
|
|
112
|
+
* Compresses textures using browser-native OffscreenCanvas encoding.
|
|
113
|
+
* No server call is made. Works in any modern browser context.
|
|
112
114
|
*
|
|
113
|
-
* @param options - Texture compression options (
|
|
115
|
+
* @param options - Texture compression options (targetFormat, quality, resize)
|
|
114
116
|
* @returns Promise that resolves when compression is complete
|
|
115
|
-
* @throws Error if
|
|
117
|
+
* @throws Error if OffscreenCanvas is unavailable or encoding fails
|
|
116
118
|
*/
|
|
117
119
|
texturesOptimization: (options?: TextureCompressOptions) => Promise<void>;
|
|
118
120
|
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates a sharp-constructor-compatible browser encoder.
|
|
3
|
+
*
|
|
4
|
+
* The returned function matches the sharp API surface that gltf-transform uses:
|
|
5
|
+
* `encoder(buffer)` → instance with `.resize()`, `.webp/jpeg/png()`,
|
|
6
|
+
* `.toBuffer()`, and `.metadata()`.
|
|
7
|
+
*
|
|
8
|
+
* Pass the result directly to `TextureCompressOptions.encoder`:
|
|
9
|
+
* ```ts
|
|
10
|
+
* await optimizer.compressTextures({
|
|
11
|
+
* encoder: createBrowserTextureEncoder(),
|
|
12
|
+
* targetFormat: 'webp',
|
|
13
|
+
* quality: 80,
|
|
14
|
+
* })
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare function createBrowserTextureEncoder(): (inputBuffer: Uint8Array | ArrayBuffer) => {
|
|
18
|
+
resize(w?: number, h?: number, _opts?: unknown): /*elided*/ any;
|
|
19
|
+
webp(opts?: {
|
|
20
|
+
quality?: number;
|
|
21
|
+
}): /*elided*/ any;
|
|
22
|
+
jpeg(opts?: {
|
|
23
|
+
quality?: number;
|
|
24
|
+
}): /*elided*/ any;
|
|
25
|
+
png(_opts?: unknown): /*elided*/ any;
|
|
26
|
+
/**
|
|
27
|
+
* sharp-compatible `.toFormat(format, opts)` — gltf-transform calls this
|
|
28
|
+
* instead of `.webp()` / `.jpeg()` / `.png()` directly.
|
|
29
|
+
*/
|
|
30
|
+
toFormat(format: string, opts?: {
|
|
31
|
+
quality?: number;
|
|
32
|
+
lossless?: boolean;
|
|
33
|
+
nearLossless?: boolean;
|
|
34
|
+
}): /*elided*/ any;
|
|
35
|
+
toBuffer(): Promise<Uint8Array>;
|
|
36
|
+
metadata(): Promise<{
|
|
37
|
+
width: number;
|
|
38
|
+
height: number;
|
|
39
|
+
format: string;
|
|
40
|
+
}>;
|
|
41
|
+
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { optimizeTextures
|
|
2
|
-
export {
|
|
1
|
+
export { optimizeTextures } from './texture-optimization';
|
|
2
|
+
export { createBrowserTextureEncoder } from './browser-texture-encoder';
|
|
@@ -1,17 +1,7 @@
|
|
|
1
1
|
import { ModelOptimizer, TextureCompressOptions } from '../../../../core/src/model-optimizer/index.ts';
|
|
2
2
|
/**
|
|
3
|
-
* Performs
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Handles client-side texture optimization (throws error as not supported in browser).
|
|
8
|
-
*/
|
|
9
|
-
export declare const performClientSideTextureOptimization: () => never;
|
|
10
|
-
/**
|
|
11
|
-
* Determines if server-side optimization should be used.
|
|
12
|
-
*/
|
|
13
|
-
export declare const shouldUseServerOptimization: (options?: TextureCompressOptions) => boolean;
|
|
14
|
-
/**
|
|
15
|
-
* Main texture optimization function that orchestrates the process.
|
|
3
|
+
* Performs client-side texture compression using the browser-native encoder.
|
|
4
|
+
* Delegates directly to optimizer.compressTextures() with the OffscreenCanvas
|
|
5
|
+
* encoder injected — no network round-trip required.
|
|
16
6
|
*/
|
|
17
7
|
export declare const optimizeTextures: (optimizer: ModelOptimizer, options?: TextureCompressOptions) => Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
let e=require("react"),t=require("@vctrl/core"),n=require("@vctrl/core/model-optimizer");var r={info:null,error:null,loading:!1,report:null},i=(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{...r};default:return e}};function a(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 o=t=>{let[n,r]=(0,e.useState)(null),i=t.report;return(0,e.useEffect)(()=>{!n&&i&&r(i)},[n,i]),{info:(0,e.useMemo)(()=>{let e=a(n),t=a(i);return{initial:{verticesCount:e.verticesCount,primitivesCount:e.primitivesCount,meshesCount:e.meshesCount,texturesCount:e.textureAssetCount,sceneBytes:e.sceneBytes},optimized:{verticesCount:t.verticesCount,primitivesCount:t.primitivesCount,meshesCount:t.meshesCount,texturesCount:t.textureAssetCount,sceneBytes:t.sceneBytes},improvement:{verticesCount:e.verticesCount-t.verticesCount,primitivesCount:e.primitivesCount-t.primitivesCount,meshesCount:e.meshesCount-t.meshesCount,texturesCount:e.textureAssetCount-t.textureAssetCount,sceneBytes:e.sceneBytes-t.sceneBytes}}},[i,n]),reset:(0,e.useCallback)(()=>{r(null)},[])}};async function s(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:1/0,n=t.maxHeight?t.maxHeight/a:1/0,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 c(){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 s(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}}var l=async(e,t)=>{e.hasModel()&&await e.compressTextures({...t,encoder:c()})},u=()=>{let[a,s]=(0,e.useReducer)(i,r),{report:c,error:u,loading:d}=a,{info:f,reset:p}=o(a),m=(0,e.useRef)(new n.ModelOptimizer);(0,e.useEffect)(()=>{let e=m.current;return()=>{e.reset()}},[]);let h=(0,e.useCallback)(async e=>{s({type:`LOAD_START`});try{let t=m.current;await t.loadFromThreeJS(e),s({type:`LOAD_SUCCESS`,payload:{report:await t.getReport()}})}catch(e){s({type:`LOAD_ERROR`,payload:e}),console.error(`Error loading model:`,e)}},[]),g=(0,e.useCallback)(async e=>{s({type:`LOAD_START`});try{let n=m.current,r=new Map;for(let n of Object.values(e.assetData??{}))for(let e of(0,t.buildAssetLookupKeys)(n.fileName))r.set(e,n);let i={},a=e.gltfJson,o=[...(a.images??[]).map(e=>e.uri).filter(e=>!!e&&!e.startsWith(`data:`)),...(a.buffers??[]).map(e=>e.uri).filter(e=>!!e&&!e.startsWith(`data:`))];for(let e of o){let n=(0,t.normalizeAssetUri)(e),a=n.split(`/`).pop()||n,o=r.get(e)??r.get(n)??r.get(a);o&&(i[e]=(0,t.toSerializedAssetBytes)(o))}let c={json:e.gltfJson,resources:i};await n.loadFromJSON(c),s({type:`LOAD_SUCCESS`,payload:{report:await n.getReport()}})}catch(e){throw s({type:`LOAD_ERROR`,payload:e}),console.error(`Error loading server scene into optimizer:`,e),e}},[]),_=(0,e.useCallback)(async e=>{s({type:`LOAD_START`});try{await m.current.loadFromBuffer(e),s({type:`LOAD_SUCCESS`,payload:{report:await m.current.getReport()}})}catch(e){throw s({type:`LOAD_ERROR`,payload:e}),console.error(`Error loading GLB buffer into optimizer:`,e),e}},[]),v=(0,e.useCallback)(async(e,t)=>{s({type:`LOAD_START`});try{await m.current.loadFromGLTFWithAssets(e,t),s({type:`LOAD_SUCCESS`,payload:{report:await m.current.getReport()}})}catch(e){throw s({type:`LOAD_ERROR`,payload:e}),console.error(`Error loading GLTF with assets into optimizer:`,e),e}},[]),y=(0,e.useCallback)(async e=>{if(m.current.hasModel())try{await m.current.simplify(e),s({type:`LOAD_SUCCESS`,payload:{report:await m.current.getReport()}})}catch(e){console.error(`Simplification failed:`,e)}},[]),b=(0,e.useCallback)(async e=>{if(m.current.hasModel())try{await m.current.deduplicate(e),s({type:`LOAD_SUCCESS`,payload:{report:await m.current.getReport()}})}catch(e){console.error(`Deduplication failed:`,e)}},[]),x=(0,e.useCallback)(async e=>{if(m.current.hasModel())try{await m.current.quantize(e),s({type:`LOAD_SUCCESS`,payload:{report:await m.current.getReport()}})}catch(e){console.error(`Quantization failed:`,e)}},[]),S=(0,e.useCallback)(async e=>{if(m.current.hasModel())try{await m.current.optimizeNormals(e),s({type:`LOAD_SUCCESS`,payload:{report:await m.current.getReport()}})}catch(e){console.error(`Normals optimization failed:`,e)}},[]),C=(0,e.useCallback)(async e=>{try{await l(m.current,e),s({type:`LOAD_SUCCESS`,payload:{report:await m.current.getReport()}})}catch(e){throw console.error(`Texture optimization failed:`,e),e}},[]),w=(0,e.useCallback)(async()=>{if(!m.current.hasModel())return null;try{return await m.current.export()}catch(e){return console.error(`Error getting model binary:`,e),null}},[]),T=(0,e.useCallback)(()=>m.current.hasModel()?m.current.document:null,[]);return{load:h,loadFromServerSceneData:g,loadFromGlbBuffer:_,loadFromGLTFWithAssets:v,getModel:w,isReady:!!T()&&!d,_getDocument:T,reset:(0,e.useCallback)(()=>{m.current.reset(),s({type:`RESET`}),p()},[p]),error:u,loading:d,report:c,info:f,simplifyOptimization:y,dedupOptimization:b,quantizeOptimization:x,normalsOptimization:S,texturesOptimization:C}};Object.defineProperty(exports,"t",{enumerable:!0,get:function(){return u}});
|