u-space 0.0.32 → 0.0.33
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/dist/index.cjs +3 -3
- package/dist/index.js +1174 -938
- package/dist/modelDecodeProtocol-CC2tkS8h.js +405 -0
- package/dist/modelDecodeProtocol-pgjndTph.cjs +1 -0
- package/dist/src/loaders/ModelLoaderManager.d.ts +13 -1
- package/dist/src/loaders/modelPersistentCache.d.ts +35 -0
- package/dist/src/objects/Model.d.ts +4 -0
- package/dist/src/worker/decodeGltfModel.d.ts +7 -1
- package/dist/src/worker/modelDecodeProtocol.d.ts +2 -0
- package/dist/worker/model-decoder.cjs +1 -1
- package/dist/worker/model-decoder.js +231 -205
- package/docs/api-objects.md +11 -2
- package/docs/changelog.md +7 -0
- package/docs/examples-guide.md +2 -2
- package/docs/getting-started.md +1 -1
- package/package.json +1 -1
- package/dist/modelDecodeProtocol-D1Apm9iG.cjs +0 -1
- package/dist/modelDecodeProtocol-wDB7ksYw.js +0 -56
|
@@ -1,95 +1,87 @@
|
|
|
1
1
|
import { g as F } from "../getTransferables-DrzSmiCR.js";
|
|
2
|
-
import { c as
|
|
3
|
-
import {
|
|
4
|
-
const
|
|
2
|
+
import { c as U } from "../workerImageUtils-mQD3TQ1N.js";
|
|
3
|
+
import { l as E, U as v, d as S, M as C, v as B } from "../modelDecodeProtocol-CC2tkS8h.js";
|
|
4
|
+
const R = "SBMG----", V = 1179937895, D = 1313821514, G = 5130562, k = new TextDecoder();
|
|
5
5
|
function g(e) {
|
|
6
|
-
throw new
|
|
6
|
+
throw new v(e);
|
|
7
7
|
}
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
let l = !
|
|
11
|
-
if (
|
|
8
|
+
function O(e, t, n, s = e, a = !1) {
|
|
9
|
+
const i = e.startsWith("data:") || e.startsWith("blob:");
|
|
10
|
+
let l = !a;
|
|
11
|
+
if (a && !i)
|
|
12
12
|
try {
|
|
13
|
-
const
|
|
14
|
-
l = (
|
|
13
|
+
const d = new URL(e), f = new URL(s);
|
|
14
|
+
l = (d.protocol === "http:" || d.protocol === "https:") && d.origin === f.origin;
|
|
15
15
|
} catch {
|
|
16
16
|
l = !1;
|
|
17
17
|
}
|
|
18
|
-
|
|
19
|
-
signal:
|
|
18
|
+
return i ? { signal: n, persistent: !1 } : {
|
|
19
|
+
signal: n,
|
|
20
|
+
persistent: t.persistent,
|
|
21
|
+
cacheMode: t.cacheMode,
|
|
22
|
+
cacheKey: t.cacheKey,
|
|
20
23
|
headers: l ? t.headers : void 0,
|
|
21
24
|
credentials: l ? t.credentials : "omit"
|
|
22
|
-
}
|
|
23
|
-
if (!h.ok)
|
|
24
|
-
throw new Error(`Failed to load model resource ${e}: ${h.status} ${h.statusText}`);
|
|
25
|
-
return h;
|
|
25
|
+
};
|
|
26
26
|
}
|
|
27
|
-
async function T(e, t, s,
|
|
28
|
-
const
|
|
27
|
+
async function T(e, t, n, s, a, i, l, d) {
|
|
28
|
+
const f = N(e, t), u = i ? await i.resolve(f, a) : { url: f, complete() {
|
|
29
29
|
} };
|
|
30
30
|
try {
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
} catch (n) {
|
|
45
|
-
if (s.aborted) throw n;
|
|
46
|
-
console.warn(`[ModelDecodeWorker] Failed to read Cache Storage for ${e}:`, n);
|
|
47
|
-
}
|
|
48
|
-
const i = await k(e, t, s);
|
|
49
|
-
if (o)
|
|
50
|
-
try {
|
|
51
|
-
await o.put(e, i.clone());
|
|
52
|
-
} catch (n) {
|
|
53
|
-
if (s.aborted) throw n;
|
|
54
|
-
console.warn(`[ModelDecodeWorker] Failed to write Cache Storage for ${e}:`, n);
|
|
31
|
+
const r = O(
|
|
32
|
+
u.url,
|
|
33
|
+
s,
|
|
34
|
+
a,
|
|
35
|
+
n,
|
|
36
|
+
!0
|
|
37
|
+
), o = await E(u.url, r);
|
|
38
|
+
o.fromCache && l.push({
|
|
39
|
+
url: u.url,
|
|
40
|
+
options: {
|
|
41
|
+
headers: r.headers,
|
|
42
|
+
credentials: r.credentials,
|
|
43
|
+
cacheKey: r.cacheKey
|
|
55
44
|
}
|
|
56
|
-
|
|
45
|
+
});
|
|
46
|
+
const c = await d(o);
|
|
47
|
+
return u.complete(), c;
|
|
48
|
+
} catch (r) {
|
|
49
|
+
throw u.complete(r), r;
|
|
57
50
|
}
|
|
58
|
-
return (await k(e, t, s)).arrayBuffer();
|
|
59
51
|
}
|
|
60
|
-
function
|
|
61
|
-
if (e.byteLength < 8 ||
|
|
62
|
-
const
|
|
63
|
-
for (let
|
|
64
|
-
const
|
|
65
|
-
|
|
52
|
+
function $(e) {
|
|
53
|
+
if (e.byteLength < 8 || k.decode(new Uint8Array(e, 0, 8)) !== R) return e;
|
|
54
|
+
const n = new Uint8Array(e.byteLength - 8), s = new Uint8Array(e, 8);
|
|
55
|
+
for (let a = 0; a < s.length; a++) {
|
|
56
|
+
const i = s[a];
|
|
57
|
+
n[a] = i >> 4 & 15 | i << 4 & 240;
|
|
66
58
|
}
|
|
67
|
-
return
|
|
59
|
+
return n.buffer;
|
|
68
60
|
}
|
|
69
|
-
function
|
|
61
|
+
function I(e) {
|
|
70
62
|
const t = new DataView(e);
|
|
71
|
-
if (e.byteLength >= 12 && t.getUint32(0, !0) ===
|
|
72
|
-
const
|
|
73
|
-
if (
|
|
74
|
-
throw new Error(`Invalid GLB length: expected ${
|
|
75
|
-
let
|
|
76
|
-
const
|
|
77
|
-
for (;
|
|
78
|
-
const l = t.getUint32(
|
|
79
|
-
if (
|
|
80
|
-
const f = e.slice(
|
|
81
|
-
|
|
63
|
+
if (e.byteLength >= 12 && t.getUint32(0, !0) === V) {
|
|
64
|
+
const n = t.getUint32(8, !0);
|
|
65
|
+
if (n > e.byteLength)
|
|
66
|
+
throw new Error(`Invalid GLB length: expected ${n}, received ${e.byteLength}.`);
|
|
67
|
+
let s = 12, a;
|
|
68
|
+
const i = [];
|
|
69
|
+
for (; s + 8 <= n; ) {
|
|
70
|
+
const l = t.getUint32(s, !0), d = t.getUint32(s + 4, !0);
|
|
71
|
+
if (s += 8, s + l > n) throw new Error("Invalid GLB chunk length.");
|
|
72
|
+
const f = e.slice(s, s + l);
|
|
73
|
+
d === D ? a = JSON.parse(k.decode(f).replace(/\u0000+$/u, "")) : d === G && i.push(f), s += l;
|
|
82
74
|
}
|
|
83
|
-
if (!
|
|
84
|
-
return { document:
|
|
75
|
+
if (!a) throw new Error("GLB does not contain a JSON chunk.");
|
|
76
|
+
return { document: a, binaryChunks: i };
|
|
85
77
|
}
|
|
86
78
|
return {
|
|
87
|
-
document: JSON.parse(
|
|
79
|
+
document: JSON.parse(k.decode(e).replace(/^\uFEFF/u, "")),
|
|
88
80
|
binaryChunks: []
|
|
89
81
|
};
|
|
90
82
|
}
|
|
91
|
-
function
|
|
92
|
-
e.asset?.version?.startsWith("2") || g("Only glTF 2.x static models are supported by the decode worker."), (e.extensionsRequired?.length || e.extensionsUsed?.length) && g("glTF extensions are not supported by the decode worker."), e.animations?.length && g("Animated glTF models must use the regular loader."), e.skins?.length && g("Skinned glTF models must use the regular loader."), e.cameras?.length && g("glTF cameras must use the regular loader."),
|
|
83
|
+
function K(e) {
|
|
84
|
+
e.asset?.version?.startsWith("2") || g("Only glTF 2.x static models are supported by the decode worker."), (e.extensionsRequired?.length || e.extensionsUsed?.length) && g("glTF extensions are not supported by the decode worker."), e.animations?.length && g("Animated glTF models must use the regular loader."), e.skins?.length && g("Skinned glTF models must use the regular loader."), e.cameras?.length && g("glTF cameras must use the regular loader."), B(e);
|
|
93
85
|
for (const t of e.accessors ?? [])
|
|
94
86
|
t.sparse && g("Sparse glTF accessors must use the regular loader."), t.bufferView === void 0 && g("Accessors without a bufferView must use the regular loader."), ["SCALAR", "VEC2", "VEC3", "VEC4"].includes(t.type) || g(`Accessor type ${t.type} must use the regular loader.`), [5120, 5121, 5122, 5123, 5125, 5126].includes(t.componentType) || g(`Accessor component type ${t.componentType} must use the regular loader.`);
|
|
95
87
|
for (const t of e.bufferViews ?? [])
|
|
@@ -97,8 +89,8 @@ function I(e) {
|
|
|
97
89
|
for (const t of e.nodes ?? [])
|
|
98
90
|
(t.skin !== void 0 || t.weights?.length || t.camera !== void 0) && g("Skinned, morphed, or camera glTF nodes must use the regular loader."), t.extensions && Object.keys(t.extensions).length > 0 && g("Extended glTF nodes must use the regular loader.");
|
|
99
91
|
for (const t of e.meshes ?? [])
|
|
100
|
-
for (const
|
|
101
|
-
(
|
|
92
|
+
for (const n of t.primitives)
|
|
93
|
+
(n.mode ?? 4) !== 4 && g("Only triangle glTF primitives are supported."), n.targets?.length && g("Morph targets must use the regular loader."), n.extensions && Object.keys(n.extensions).length > 0 && g("Extended glTF primitives must use the regular loader.");
|
|
102
94
|
for (const t of e.materials ?? [])
|
|
103
95
|
t.extensions && Object.keys(t.extensions).length > 0 && g("Extended glTF materials must use the regular loader.");
|
|
104
96
|
for (const t of e.textures ?? [])
|
|
@@ -107,233 +99,267 @@ function I(e) {
|
|
|
107
99
|
function N(e, t) {
|
|
108
100
|
return e.startsWith("data:") || e.startsWith("blob:") ? e : new URL(e, t).href;
|
|
109
101
|
}
|
|
110
|
-
async function _(e, t, s,
|
|
111
|
-
const
|
|
112
|
-
for (const
|
|
113
|
-
let
|
|
114
|
-
if (
|
|
115
|
-
|
|
116
|
-
|
|
102
|
+
async function _(e, t, n, s, a, i, l, d = []) {
|
|
103
|
+
const f = [...t], u = [];
|
|
104
|
+
for (const r of e.buffers ?? []) {
|
|
105
|
+
let o;
|
|
106
|
+
if (r.uri)
|
|
107
|
+
o = await T(
|
|
108
|
+
r.uri,
|
|
109
|
+
n,
|
|
117
110
|
s,
|
|
118
|
-
|
|
111
|
+
a,
|
|
119
112
|
i,
|
|
120
|
-
n,
|
|
121
113
|
l,
|
|
122
|
-
|
|
114
|
+
d,
|
|
115
|
+
(c) => c.buffer
|
|
123
116
|
);
|
|
124
117
|
else {
|
|
125
|
-
const
|
|
126
|
-
if (!
|
|
127
|
-
|
|
118
|
+
const c = f.shift();
|
|
119
|
+
if (!c) throw new Error("glTF buffer is missing its GLB binary chunk.");
|
|
120
|
+
o = c;
|
|
128
121
|
}
|
|
129
|
-
if (
|
|
130
|
-
throw new Error(`glTF buffer is truncated: expected ${
|
|
131
|
-
|
|
122
|
+
if (o.byteLength < r.byteLength)
|
|
123
|
+
throw new Error(`glTF buffer is truncated: expected ${r.byteLength}, received ${o.byteLength}.`);
|
|
124
|
+
u.push(o), Reflect.deleteProperty(r, "uri");
|
|
132
125
|
}
|
|
133
|
-
return
|
|
126
|
+
return u;
|
|
134
127
|
}
|
|
135
|
-
function
|
|
136
|
-
const
|
|
137
|
-
if (!
|
|
138
|
-
const
|
|
139
|
-
if (!
|
|
140
|
-
const l =
|
|
141
|
-
return new Blob([new Uint8Array(
|
|
142
|
-
type:
|
|
128
|
+
function j(e, t, n, s) {
|
|
129
|
+
const a = e.bufferViews?.[n];
|
|
130
|
+
if (!a) throw new Error(`glTF image references missing bufferView ${n}.`);
|
|
131
|
+
const i = t[a.buffer];
|
|
132
|
+
if (!i) throw new Error(`glTF image references missing buffer ${a.buffer}.`);
|
|
133
|
+
const l = a.byteOffset ?? 0;
|
|
134
|
+
return new Blob([new Uint8Array(i, l, a.byteLength)], {
|
|
135
|
+
type: s ?? "application/octet-stream"
|
|
143
136
|
});
|
|
144
137
|
}
|
|
145
|
-
async function
|
|
146
|
-
const
|
|
138
|
+
async function P(e, t, n, s, a, i, l, d = []) {
|
|
139
|
+
const f = [], u = [];
|
|
147
140
|
try {
|
|
148
|
-
for (const
|
|
149
|
-
let
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
|
|
141
|
+
for (const r of e.images ?? []) {
|
|
142
|
+
let o;
|
|
143
|
+
if (r.uri)
|
|
144
|
+
o = await T(
|
|
145
|
+
r.uri,
|
|
146
|
+
n,
|
|
153
147
|
s,
|
|
154
|
-
|
|
148
|
+
a,
|
|
155
149
|
i,
|
|
156
|
-
n,
|
|
157
150
|
l,
|
|
158
|
-
|
|
151
|
+
d,
|
|
152
|
+
(p) => new Blob([p.buffer], {
|
|
153
|
+
type: p.headers.get("content-type") ?? "application/octet-stream"
|
|
154
|
+
})
|
|
159
155
|
);
|
|
160
|
-
else if (
|
|
161
|
-
|
|
156
|
+
else if (r.bufferView !== void 0)
|
|
157
|
+
o = j(e, t, r.bufferView, r.mimeType);
|
|
162
158
|
else
|
|
163
159
|
throw new Error("glTF image is missing both uri and bufferView.");
|
|
164
|
-
if (
|
|
165
|
-
const
|
|
166
|
-
if (
|
|
167
|
-
const
|
|
168
|
-
|
|
160
|
+
if (i.aborted) throw i.reason ?? new DOMException("Aborted", "AbortError");
|
|
161
|
+
const c = await globalThis.crypto.subtle.digest("SHA-256", await o.arrayBuffer());
|
|
162
|
+
if (i.aborted) throw i.reason ?? new DOMException("Aborted", "AbortError");
|
|
163
|
+
const h = Array.from(new Uint8Array(c), (p) => p.toString(16).padStart(2, "0")).join("");
|
|
164
|
+
u.push(`${o.type}:${o.size}:${h}`), f.push(await U(o, { imageOrientation: "flipY" })), Reflect.deleteProperty(r, "uri");
|
|
169
165
|
}
|
|
170
|
-
return { images:
|
|
171
|
-
} catch (
|
|
172
|
-
throw
|
|
166
|
+
return { images: f, imageKeys: u };
|
|
167
|
+
} catch (r) {
|
|
168
|
+
throw f.forEach((o) => o.close()), r;
|
|
173
169
|
}
|
|
174
170
|
}
|
|
175
|
-
async function
|
|
176
|
-
const
|
|
177
|
-
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
h,
|
|
171
|
+
async function A(e, t, n = {}, s = new AbortController().signal, a, i = []) {
|
|
172
|
+
const l = $(t), { document: d, binaryChunks: f } = I(l);
|
|
173
|
+
K(d);
|
|
174
|
+
const u = n.resourceBaseUrl ?? e, r = await _(
|
|
175
|
+
d,
|
|
181
176
|
f,
|
|
177
|
+
u,
|
|
182
178
|
e,
|
|
179
|
+
n,
|
|
183
180
|
s,
|
|
184
|
-
|
|
181
|
+
a,
|
|
185
182
|
i
|
|
186
|
-
), { images:
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
183
|
+
), { images: o, imageKeys: c } = await P(
|
|
184
|
+
d,
|
|
185
|
+
r,
|
|
186
|
+
u,
|
|
190
187
|
e,
|
|
188
|
+
n,
|
|
191
189
|
s,
|
|
192
|
-
|
|
190
|
+
a,
|
|
193
191
|
i
|
|
194
192
|
);
|
|
195
193
|
return {
|
|
196
|
-
protocolVersion:
|
|
194
|
+
protocolVersion: C,
|
|
197
195
|
kind: "gltf-static",
|
|
198
196
|
url: e,
|
|
199
|
-
document:
|
|
200
|
-
buffers:
|
|
201
|
-
images:
|
|
202
|
-
imageKeys:
|
|
197
|
+
document: d,
|
|
198
|
+
buffers: r,
|
|
199
|
+
images: o,
|
|
200
|
+
imageKeys: c
|
|
203
201
|
};
|
|
204
202
|
}
|
|
205
|
-
async function q(e, t = {},
|
|
206
|
-
const
|
|
207
|
-
|
|
203
|
+
async function q(e, t = {}, n = new AbortController().signal, s) {
|
|
204
|
+
const a = O(e, t, n), i = [], l = await E(e, a);
|
|
205
|
+
l.fromCache && i.push({
|
|
206
|
+
url: e,
|
|
207
|
+
options: {
|
|
208
|
+
headers: a.headers,
|
|
209
|
+
credentials: a.credentials,
|
|
210
|
+
cacheKey: a.cacheKey
|
|
211
|
+
}
|
|
212
|
+
});
|
|
213
|
+
try {
|
|
214
|
+
return await A(
|
|
215
|
+
e,
|
|
216
|
+
l.buffer,
|
|
217
|
+
t,
|
|
218
|
+
n,
|
|
219
|
+
s,
|
|
220
|
+
i
|
|
221
|
+
);
|
|
222
|
+
} catch (d) {
|
|
223
|
+
if (n.aborted || d instanceof v || i.length === 0) throw d;
|
|
224
|
+
await Promise.all(i.map((u) => S(u.url, u.options)));
|
|
225
|
+
const f = await E(e, a);
|
|
226
|
+
return A(
|
|
227
|
+
e,
|
|
228
|
+
f.buffer,
|
|
229
|
+
t,
|
|
230
|
+
n,
|
|
231
|
+
s
|
|
232
|
+
);
|
|
233
|
+
}
|
|
208
234
|
}
|
|
209
|
-
function
|
|
235
|
+
function M(e) {
|
|
210
236
|
return e instanceof Error ? { message: e.message, name: e.name, stack: e.stack } : { message: String(e) };
|
|
211
237
|
}
|
|
212
238
|
function z(e = globalThis) {
|
|
213
|
-
const t = [],
|
|
214
|
-
let
|
|
215
|
-
const l = (r) => new Promise((
|
|
216
|
-
r.acknowledge =
|
|
217
|
-
}),
|
|
218
|
-
for (const
|
|
219
|
-
|
|
239
|
+
const t = [], n = /* @__PURE__ */ new Set();
|
|
240
|
+
let s = null, a = !1, i = !1;
|
|
241
|
+
const l = (r) => new Promise((o) => {
|
|
242
|
+
r.acknowledge = o;
|
|
243
|
+
}), d = (r, o) => {
|
|
244
|
+
for (const c of r.resources.values())
|
|
245
|
+
c.cleanup(), c.reject(o);
|
|
220
246
|
r.resources.clear();
|
|
221
247
|
}, f = (r) => ({
|
|
222
|
-
async resolve(
|
|
223
|
-
if (
|
|
224
|
-
const
|
|
225
|
-
const
|
|
226
|
-
r.resources.delete(
|
|
227
|
-
}, y = () =>
|
|
228
|
-
r.resources.set(
|
|
248
|
+
async resolve(o, c) {
|
|
249
|
+
if (c.aborted) throw c.reason ?? new DOMException("Aborted", "AbortError");
|
|
250
|
+
const h = r.nextResourceId++, p = await new Promise((m, b) => {
|
|
251
|
+
const L = () => {
|
|
252
|
+
r.resources.delete(h), b(c.reason ?? new DOMException("Aborted", "AbortError"));
|
|
253
|
+
}, y = () => c.removeEventListener("abort", L);
|
|
254
|
+
r.resources.set(h, {
|
|
229
255
|
resolve(w) {
|
|
230
|
-
y(), r.resources.delete(
|
|
256
|
+
y(), r.resources.delete(h), m(w);
|
|
231
257
|
},
|
|
232
258
|
reject(w) {
|
|
233
|
-
y(), r.resources.delete(
|
|
259
|
+
y(), r.resources.delete(h), b(w);
|
|
234
260
|
},
|
|
235
261
|
cleanup: y
|
|
236
|
-
}),
|
|
262
|
+
}), c.addEventListener("abort", L, { once: !0 });
|
|
237
263
|
try {
|
|
238
|
-
e.postMessage({ type: "resolve-resource", id: r.id, resourceId:
|
|
264
|
+
e.postMessage({ type: "resolve-resource", id: r.id, resourceId: h, url: o });
|
|
239
265
|
} catch (w) {
|
|
240
|
-
y(), r.resources.delete(
|
|
266
|
+
y(), r.resources.delete(h), b(w instanceof Error ? w : new Error(String(w)));
|
|
241
267
|
}
|
|
242
268
|
});
|
|
243
|
-
let
|
|
269
|
+
let x = !1;
|
|
244
270
|
return {
|
|
245
|
-
url:
|
|
246
|
-
complete(
|
|
247
|
-
|
|
271
|
+
url: p,
|
|
272
|
+
complete(m) {
|
|
273
|
+
x || (x = !0, e.postMessage({
|
|
248
274
|
type: "resource-complete",
|
|
249
275
|
id: r.id,
|
|
250
|
-
resourceId:
|
|
251
|
-
ok:
|
|
252
|
-
error:
|
|
276
|
+
resourceId: h,
|
|
277
|
+
ok: m === void 0,
|
|
278
|
+
error: m === void 0 ? void 0 : M(m)
|
|
253
279
|
}));
|
|
254
280
|
}
|
|
255
281
|
};
|
|
256
282
|
}
|
|
257
|
-
}),
|
|
258
|
-
if (!(
|
|
259
|
-
|
|
283
|
+
}), u = async () => {
|
|
284
|
+
if (!(a || i)) {
|
|
285
|
+
a = !0;
|
|
260
286
|
try {
|
|
261
|
-
for (; !
|
|
287
|
+
for (; !i && t.length > 0; ) {
|
|
262
288
|
const r = t.shift();
|
|
263
|
-
if (
|
|
264
|
-
const
|
|
289
|
+
if (n.delete(r.id)) continue;
|
|
290
|
+
const o = {
|
|
265
291
|
id: r.id,
|
|
266
292
|
controller: new AbortController(),
|
|
267
293
|
nextResourceId: 1,
|
|
268
294
|
resources: /* @__PURE__ */ new Map()
|
|
269
295
|
};
|
|
270
|
-
|
|
296
|
+
s = o;
|
|
271
297
|
try {
|
|
272
|
-
const
|
|
298
|
+
const c = await q(
|
|
273
299
|
r.url,
|
|
274
300
|
r.options,
|
|
275
|
-
|
|
276
|
-
f(
|
|
301
|
+
o.controller.signal,
|
|
302
|
+
f(o)
|
|
277
303
|
);
|
|
278
|
-
if (
|
|
279
|
-
|
|
304
|
+
if (n.delete(r.id) || i) {
|
|
305
|
+
c.images.forEach((h) => h.close());
|
|
280
306
|
continue;
|
|
281
307
|
}
|
|
282
308
|
try {
|
|
283
309
|
e.postMessage(
|
|
284
|
-
{ type: "decoded", id: r.id, packet:
|
|
285
|
-
F(
|
|
310
|
+
{ type: "decoded", id: r.id, packet: c },
|
|
311
|
+
F(c)
|
|
286
312
|
);
|
|
287
|
-
} catch (
|
|
288
|
-
throw
|
|
313
|
+
} catch (h) {
|
|
314
|
+
throw c.images.forEach((p) => p.close()), h;
|
|
289
315
|
}
|
|
290
|
-
await l(
|
|
291
|
-
} catch (
|
|
292
|
-
const
|
|
293
|
-
if (
|
|
294
|
-
|
|
316
|
+
await l(o);
|
|
317
|
+
} catch (c) {
|
|
318
|
+
const h = n.delete(r.id);
|
|
319
|
+
if (o.controller.signal.aborted || h || i) continue;
|
|
320
|
+
c instanceof v ? e.postMessage({ type: "unsupported", id: r.id, reason: c.message }) : e.postMessage({ type: "error", id: r.id, error: M(c) });
|
|
295
321
|
} finally {
|
|
296
|
-
|
|
322
|
+
d(o, new DOMException("Model decode request ended.", "AbortError")), n.delete(r.id), s === o && (s = null);
|
|
297
323
|
}
|
|
298
324
|
}
|
|
299
325
|
} finally {
|
|
300
|
-
|
|
326
|
+
a = !1, !i && t.length > 0 && u();
|
|
301
327
|
}
|
|
302
328
|
}
|
|
303
329
|
};
|
|
304
330
|
e.addEventListener("message", (r) => {
|
|
305
|
-
const
|
|
306
|
-
if (
|
|
307
|
-
if (
|
|
308
|
-
t.push(
|
|
331
|
+
const o = r.data;
|
|
332
|
+
if (o.type === "decode") {
|
|
333
|
+
if (i) return;
|
|
334
|
+
t.push(o), u();
|
|
309
335
|
return;
|
|
310
336
|
}
|
|
311
|
-
if (
|
|
312
|
-
|
|
337
|
+
if (o.type === "cancel") {
|
|
338
|
+
n.add(o.id), s?.id === o.id && (s.controller.abort(), s.acknowledge?.());
|
|
313
339
|
return;
|
|
314
340
|
}
|
|
315
|
-
if (
|
|
316
|
-
|
|
341
|
+
if (o.type === "ack") {
|
|
342
|
+
s?.id === o.id && s.acknowledge?.();
|
|
317
343
|
return;
|
|
318
344
|
}
|
|
319
|
-
if (
|
|
320
|
-
if (
|
|
321
|
-
const
|
|
322
|
-
if (!
|
|
323
|
-
if (
|
|
324
|
-
|
|
345
|
+
if (o.type === "resource-resolved") {
|
|
346
|
+
if (s?.id !== o.id) return;
|
|
347
|
+
const c = s.resources.get(o.resourceId);
|
|
348
|
+
if (!c) return;
|
|
349
|
+
if (o.url !== void 0)
|
|
350
|
+
c.resolve(o.url);
|
|
325
351
|
else {
|
|
326
|
-
const
|
|
327
|
-
|
|
352
|
+
const h = new Error(o.error?.message ?? "Failed to resolve model resource URL.");
|
|
353
|
+
h.name = o.error?.name ?? "Error", h.stack = o.error?.stack, c.reject(h);
|
|
328
354
|
}
|
|
329
355
|
return;
|
|
330
356
|
}
|
|
331
|
-
|
|
357
|
+
o.type === "dispose" && (i = !0, t.length = 0, s?.controller.abort(), s?.acknowledge?.(), e.postMessage({ type: "disposed" }), e.close());
|
|
332
358
|
});
|
|
333
359
|
}
|
|
334
360
|
export {
|
|
335
|
-
|
|
336
|
-
|
|
361
|
+
C as MODEL_DECODE_PROTOCOL_VERSION,
|
|
362
|
+
v as UnsupportedModelDecodeError,
|
|
337
363
|
z as installModelDecodeWorker,
|
|
338
|
-
|
|
364
|
+
B as validateDecodedGltfNodeGraph
|
|
339
365
|
};
|
package/docs/api-objects.md
CHANGED
|
@@ -256,8 +256,10 @@ await myModel.loadAsync(parameters: ModelParameters)
|
|
|
256
256
|
| 属性 | 类型 | 默认值 | 说明 |
|
|
257
257
|
| :----------- | :-------- | :----- | :-------------------------------------------------------------------------------------- |
|
|
258
258
|
| `url` | `string` | — | `.gltf` 或 `.glb` 文件的 URL。 |
|
|
259
|
-
| `cache` | `boolean` | `true` |
|
|
260
|
-
| `persistent` | `boolean` | `true` | 使用浏览器 Cache API
|
|
259
|
+
| `cache` | `boolean` | `true` | 启用内存 LRU 缓存。若同一请求已加载,则直接从内存中获取并克隆;最多保留 32 项。 |
|
|
260
|
+
| `persistent` | `boolean` | `true` | 使用浏览器 Cache API 持久化模型及外部 glTF 依赖;最多保留 64 项或 512 MiB。 |
|
|
261
|
+
| `cacheMode` | `string` | — | 覆盖持久化缓存策略:`no-store`、`reload`、`revalidate` 或 `force-cache`。 |
|
|
262
|
+
| `cacheKey` | `string` | — | 为同一 URL 指定版本化缓存身份;适用于发布后 URL 不变的模型。 |
|
|
261
263
|
|
|
262
264
|
#### 缓存示例
|
|
263
265
|
|
|
@@ -273,8 +275,14 @@ model2.loadAsync({ url: 'model.glb', cache: false });
|
|
|
273
275
|
// 完全禁用缓存
|
|
274
276
|
const model3 = new Model();
|
|
275
277
|
model3.loadAsync({ url: 'model.glb', cache: false, persistent: false });
|
|
278
|
+
|
|
279
|
+
// 发布新模型但 URL 保持不变时,获取网络版本并更新该版本条目
|
|
280
|
+
const model4 = new Model();
|
|
281
|
+
model4.loadAsync({ url: 'model.glb', cacheMode: 'reload', cacheKey: '2026-09-02' });
|
|
276
282
|
```
|
|
277
283
|
|
|
284
|
+
持久化缓存保留响应类型、验证器和真实字节长度。默认无服务端缓存指令的资源可复用 7 天;到期后优先使用 `ETag` / `Last-Modified` 条件验证,断网时可回退到已存在的非空缓存。`no-store` 不读取或写入持久化缓存;`reload` 强制网络请求并更新缓存;`revalidate` 即使缓存仍新鲜也会条件验证;`force-cache` 优先返回已有的非空缓存。`Cache-Control: no-store/private` 与 `Vary: Cookie/*` 响应不会写入缓存。空缓存、截断的外部 buffer、解析失败或缓存命中时未解码的贴图会删除相关缓存资源并重新请求一次。请求头、credentials 模式和 `cacheKey` 共同参与缓存身份,避免不同授权请求或版本共用同一条目。
|
|
285
|
+
|
|
278
286
|
### 动画
|
|
279
287
|
|
|
280
288
|
`Model` 在加载含内置动画的模型(如 glTF/FBX)后,会自动初始化 `AnimationMixer` 并收集 `AnimationClip`。
|
|
@@ -372,6 +380,7 @@ myModel.setMaterial(new MeshStandardMaterial({ color: 0xff0000 }));
|
|
|
372
380
|
|
|
373
381
|
- `Model.clearMemoryCache()`:清除运行时内存缓存。
|
|
374
382
|
- `await Model.clearPersistentCache()`:完全清除浏览器中 `u-space` 模型的持久化缓存。
|
|
383
|
+
- `await ModelLoaderManager.deletePersistentCache(url, { cacheKey })`:仅删除指定 URL 与版本身份的持久化缓存。
|
|
375
384
|
|
|
376
385
|
## 网格
|
|
377
386
|
|
package/docs/changelog.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## 未发布
|
|
4
4
|
|
|
5
|
+
## 0.0.33
|
|
6
|
+
|
|
7
|
+
### 修复与优化
|
|
8
|
+
|
|
9
|
+
- **模型持久化缓存**:主线程与 decode Worker 统一使用带版本、容量和响应元数据的 Cache Storage 策略,缓存项记录真实 `Content-Length`;模型及外部 glTF buffer/贴图支持过期与 `ETag` / `Last-Modified` 条件验证、离线旧缓存、授权请求隔离和损坏缓存单次自愈。调用方可通过 `cacheMode` 跳过、刷新、重验或强制读取缓存,通过 `cacheKey` 隔离同一 URL 的模型版本并精确删除目标缓存。对象内存缓存改为 32 项 LRU,磁盘缓存限制为 64 项或 512 MiB,并遵循 `no-store/private` 与敏感 `Vary` 响应。
|
|
10
|
+
- **UManager 示例**:目标对象不存在、不是 Mesh 或使用多材质时,示例不再直接写入材质属性而导致页面报错。
|
|
11
|
+
|
|
5
12
|
## 0.0.32
|
|
6
13
|
|
|
7
14
|
### 修复与优化
|
package/docs/examples-guide.md
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
## 运行方式与版本
|
|
6
6
|
|
|
7
|
-
示例统一通过 `examples/importmap.js` 注入 Import Map。本地通过 `localhost`、`127.0.0.1`、`0.0.0.0` 或 `192.168.x.x` 访问时会加载仓库里的 `../dist/` 构建产物;在线部署或非本地域名访问时会从 jsDelivr 加载当前发布版本 `u-space@0.0.
|
|
7
|
+
示例统一通过 `examples/importmap.js` 注入 Import Map。本地通过 `localhost`、`127.0.0.1`、`0.0.0.0` 或 `192.168.x.x` 访问时会加载仓库里的 `../dist/` 构建产物;在线部署或非本地域名访问时会从 jsDelivr 加载当前发布版本 `u-space@0.0.33`。Vercel 文档部署会继续把 `__VERSION__` 占位符替换为 `package.json` 中的版本号,源码里的 `0.0.33` 作为直接托管 `examples/` 时的 fallback。
|
|
8
8
|
|
|
9
9
|
插件示例可以在页面加载 `importmap.js` 前通过 `window.__IMPORTS__` 声明额外依赖。将 `u-space/plugins/<name>` 的值设为 `true` 时,`importmap.js` 会自动在本地和 CDN 路径之间切换。
|
|
10
10
|
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
- 使用全局 `Model` 类通过 `Model.prototype.loadAsync()` 异步加载 `.glb` 文件。
|
|
30
30
|
- 理解 `cache: true`(将模型节点保留在内存中,可实现即时克隆/实例化)。
|
|
31
|
-
- 理解 `persistent: true`(使用
|
|
31
|
+
- 理解 `persistent: true`(使用 Cache Storage API 持久化模型及外部 glTF 依赖,无需注册 Service Worker)。
|
|
32
32
|
- 演示如何将这些异步加载的资源安全地注册到 `viewer.objectManager`。
|
|
33
33
|
- 提供通过 `Model.clearMemoryCache()` 和 `Model.clearPersistentCache()` 手动清除缓存的示例。
|
|
34
34
|
|