@harpern/edu-modelhub-viewer-core 0.1.0-beta.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 +31 -0
- package/LICENSE +201 -0
- package/README.md +88 -0
- package/dist/announce.d.ts +25 -0
- package/dist/backgrounds.d.ts +40 -0
- package/dist/camera.d.ts +77 -0
- package/dist/coordinates.d.ts +40 -0
- package/dist/dispose.d.ts +10 -0
- package/dist/framing.d.ts +31 -0
- package/dist/index.cjs +21 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +625 -0
- package/dist/lighting.d.ts +64 -0
- package/dist/markers.d.ts +75 -0
- package/dist/metadata.d.ts +85 -0
- package/dist/normalize.d.ts +76 -0
- package/dist/occlusion.d.ts +38 -0
- package/dist/regions.d.ts +52 -0
- package/dist/transcript.d.ts +26 -0
- package/package.json +77 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,625 @@
|
|
|
1
|
+
import { parseGlb as B, validate as S, adaptSidecar as R } from "@osuecampus/edu-modelhub-extractor";
|
|
2
|
+
import { GlbParseError as ze } from "@osuecampus/edu-modelhub-extractor";
|
|
3
|
+
import { SpriteMaterial as _, CanvasTexture as N, Sprite as D, Vector3 as h, Raycaster as C, AmbientLight as L, SpotLight as U, Color as I, DirectionalLight as F, Box3 as g } from "three";
|
|
4
|
+
function W(e) {
|
|
5
|
+
return e.filter((t) => t.severity === "error");
|
|
6
|
+
}
|
|
7
|
+
function z(e) {
|
|
8
|
+
const [t, i = ""] = e.split("?", 2);
|
|
9
|
+
if (!/\.glb$/i.test(t)) return null;
|
|
10
|
+
const n = t.replace(/\.glb$/i, ".modelhub.json");
|
|
11
|
+
return i ? `${n}?${i}` : n;
|
|
12
|
+
}
|
|
13
|
+
function le(e, t) {
|
|
14
|
+
var r;
|
|
15
|
+
if (!e || e.startsWith("blob:") || e.startsWith("data:")) return "";
|
|
16
|
+
const i = t ?? ((r = globalThis.location) == null ? void 0 : r.href), n = i ? new URL(e, i).href : e, o = n.lastIndexOf("/");
|
|
17
|
+
return o === -1 ? "" : n.slice(0, o + 1);
|
|
18
|
+
}
|
|
19
|
+
async function P(e, t = fetch) {
|
|
20
|
+
const i = await t(e);
|
|
21
|
+
if (!i.ok)
|
|
22
|
+
throw new Error(`Failed to fetch ${e} (HTTP ${i.status}).`);
|
|
23
|
+
return i.arrayBuffer();
|
|
24
|
+
}
|
|
25
|
+
function G(e) {
|
|
26
|
+
const t = B(e);
|
|
27
|
+
if (!t)
|
|
28
|
+
return {
|
|
29
|
+
payload: null,
|
|
30
|
+
source: "none",
|
|
31
|
+
findings: [],
|
|
32
|
+
problem: "This model carries no EDU_modelhub metadata."
|
|
33
|
+
};
|
|
34
|
+
const i = S(t);
|
|
35
|
+
return { payload: t, source: "embedded", findings: i, problem: null };
|
|
36
|
+
}
|
|
37
|
+
async function ue(e, t = {}) {
|
|
38
|
+
const i = t.fetchImpl ?? fetch, n = await P(e, i), o = G(n);
|
|
39
|
+
if (o.payload) return { ...o, bytes: n };
|
|
40
|
+
if (t.sidecar === !1) return { ...o, bytes: n };
|
|
41
|
+
const s = (t.sidecarUrl ?? z)(e);
|
|
42
|
+
if (!s) return { ...o, bytes: n };
|
|
43
|
+
try {
|
|
44
|
+
const c = await i(s);
|
|
45
|
+
if (c.ok) {
|
|
46
|
+
const u = R(await c.json());
|
|
47
|
+
return {
|
|
48
|
+
payload: u,
|
|
49
|
+
source: "sidecar",
|
|
50
|
+
findings: S(u),
|
|
51
|
+
problem: null,
|
|
52
|
+
bytes: n
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
} catch {
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
payload: null,
|
|
59
|
+
source: "none",
|
|
60
|
+
findings: [],
|
|
61
|
+
problem: "This model has no EDU_modelhub metadata, either embedded in the file or in a sidecar beside it.",
|
|
62
|
+
bytes: n
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function de(e, t = "EDU_modelhub", i = console) {
|
|
66
|
+
if (!(e != null && e.length)) return;
|
|
67
|
+
const n = W(e), o = e.filter((r) => r.severity !== "error");
|
|
68
|
+
n.length && i.warn(`${t}: ${n.length} validation error(s)`, n), o.length && i.info(
|
|
69
|
+
`${t}: ${o.length} validation warning(s)`,
|
|
70
|
+
o
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
function v(e) {
|
|
74
|
+
return Array.isArray(e) && e.length === 3 && e.every((t) => typeof t == "number" && Number.isFinite(t));
|
|
75
|
+
}
|
|
76
|
+
function O(e) {
|
|
77
|
+
return e === "blender";
|
|
78
|
+
}
|
|
79
|
+
function x(e, t) {
|
|
80
|
+
if (!v(e)) return null;
|
|
81
|
+
const [i, n, o] = e;
|
|
82
|
+
return O(t) ? [i, o, -n] : [i, n, o];
|
|
83
|
+
}
|
|
84
|
+
function fe(e, t) {
|
|
85
|
+
if (!v(e)) return null;
|
|
86
|
+
const [i, n, o] = e;
|
|
87
|
+
return O(t) ? [i, -o, n] : [i, n, o];
|
|
88
|
+
}
|
|
89
|
+
function V(e) {
|
|
90
|
+
if (!Array.isArray(e)) return [];
|
|
91
|
+
const t = /* @__PURE__ */ new Set();
|
|
92
|
+
return e.map((i, n) => {
|
|
93
|
+
const r = (typeof (i == null ? void 0 : i.id) == "string" ? i.id.trim() : "") || `annotation-${n + 1}`;
|
|
94
|
+
let s = r, c = 2;
|
|
95
|
+
for (; t.has(s); ) s = `${r}-${c++}`;
|
|
96
|
+
return t.add(s), { ...i ?? {}, id: s };
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
function w(e) {
|
|
100
|
+
const t = V(e == null ? void 0 : e.annotations), i = (n) => Number.isFinite(n.readingOrder) ? n.readingOrder : Number.MAX_SAFE_INTEGER;
|
|
101
|
+
return t.map((n, o) => ({ annotation: n, index: o })).sort(
|
|
102
|
+
(n, o) => i(n.annotation) - i(o.annotation) || n.index - o.index
|
|
103
|
+
).map((n) => n.annotation);
|
|
104
|
+
}
|
|
105
|
+
function k(e) {
|
|
106
|
+
return (Array.isArray(e == null ? void 0 : e.regions) ? e.regions : []).map((i, n) => ({ region: i, index: n })).sort(
|
|
107
|
+
(i, n) => (i.region.order ?? Number.MAX_SAFE_INTEGER) - (n.region.order ?? Number.MAX_SAFE_INTEGER) || i.index - n.index
|
|
108
|
+
).map((i) => i.region);
|
|
109
|
+
}
|
|
110
|
+
function T(e) {
|
|
111
|
+
var t;
|
|
112
|
+
return e ? ((t = e.label) == null ? void 0 : t.trim()) || e.id || "Unnamed region" : "Unknown region";
|
|
113
|
+
}
|
|
114
|
+
function me(e) {
|
|
115
|
+
if (!e) return [];
|
|
116
|
+
const t = [], i = /\[([^\]]+)\]\((https?:\/\/[^\s)]+)\)/g;
|
|
117
|
+
let n = 0, o;
|
|
118
|
+
for (; (o = i.exec(e)) !== null; )
|
|
119
|
+
o.index > n && t.push({ text: e.slice(n, o.index) }), t.push({ text: o[1], url: o[2] }), n = o.index + o[0].length;
|
|
120
|
+
return n < e.length && t.push({ text: e.slice(n) }), t.filter((r) => r.text.trim() || r.url);
|
|
121
|
+
}
|
|
122
|
+
function pe(e) {
|
|
123
|
+
const t = e == null ? void 0 : e.coordinateSpace, i = k(e), n = w(e), o = new Map(i.map((a) => [a.id, a])), r = new Map(
|
|
124
|
+
n.map((a) => [a.id, a])
|
|
125
|
+
), s = /* @__PURE__ */ new Map();
|
|
126
|
+
for (const a of n) {
|
|
127
|
+
const l = a.regionId;
|
|
128
|
+
if (!l) continue;
|
|
129
|
+
const d = s.get(l);
|
|
130
|
+
d ? d.push(a) : s.set(l, [a]);
|
|
131
|
+
}
|
|
132
|
+
const c = [];
|
|
133
|
+
for (const a of n) {
|
|
134
|
+
const l = a.anchor;
|
|
135
|
+
if (!l || l.type !== "marker") continue;
|
|
136
|
+
const d = x(l.position, t);
|
|
137
|
+
if (!d) continue;
|
|
138
|
+
const f = a.camera, m = f ? x(f.eye, t) : null, p = f ? x(f.target, t) : null;
|
|
139
|
+
c.push({
|
|
140
|
+
annotation: a,
|
|
141
|
+
position: d,
|
|
142
|
+
...m && p ? {
|
|
143
|
+
camera: {
|
|
144
|
+
eye: m,
|
|
145
|
+
target: p,
|
|
146
|
+
...f != null && f.fov ? { fov: f.fov } : {}
|
|
147
|
+
}
|
|
148
|
+
} : {}
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
const u = new Set(
|
|
152
|
+
i.filter((a) => a.visibilityDefault === "hidden").map((a) => a.id)
|
|
153
|
+
);
|
|
154
|
+
return {
|
|
155
|
+
payload: e,
|
|
156
|
+
regions: i,
|
|
157
|
+
regionsById: o,
|
|
158
|
+
annotations: n,
|
|
159
|
+
annotationsById: r,
|
|
160
|
+
annotationsByRegion: s,
|
|
161
|
+
markerAnchors: c,
|
|
162
|
+
hiddenRegionIds: u
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function H(e) {
|
|
166
|
+
return e.isMesh === !0;
|
|
167
|
+
}
|
|
168
|
+
function he(e, t, i = []) {
|
|
169
|
+
var u;
|
|
170
|
+
const n = /* @__PURE__ */ new Map(), o = /* @__PURE__ */ new Map(), r = /* @__PURE__ */ new Map(), s = /* @__PURE__ */ new Map();
|
|
171
|
+
for (const [a, l] of ((t == null ? void 0 : t.meshes) ?? []).entries())
|
|
172
|
+
for (const d of l.primitives ?? []) {
|
|
173
|
+
const f = (u = d.extras) == null ? void 0 : u.modelhubRegionId;
|
|
174
|
+
typeof f == "string" && f && (s.set(a, f), l.name && o.set(l.name, f));
|
|
175
|
+
}
|
|
176
|
+
for (const a of (t == null ? void 0 : t.nodes) ?? []) {
|
|
177
|
+
if (a.mesh === void 0 || !a.name) continue;
|
|
178
|
+
const l = s.get(a.mesh);
|
|
179
|
+
l && r.set(a.name, l);
|
|
180
|
+
}
|
|
181
|
+
const c = new Set(i.map((a) => a.id));
|
|
182
|
+
return e.traverse((a) => {
|
|
183
|
+
var m, p;
|
|
184
|
+
if (!H(a)) return;
|
|
185
|
+
const l = (p = (m = a.geometry) == null ? void 0 : m.userData) == null ? void 0 : p.modelhubRegionId, d = typeof l == "string" && l || r.get(a.name) || o.get(a.name) || (c.has(a.name) ? a.name : void 0);
|
|
186
|
+
if (!d) return;
|
|
187
|
+
const f = n.get(d);
|
|
188
|
+
f ? f.push(a) : n.set(d, [a]), a.userData.modelhubOriginalMaterial === void 0 && (a.userData.modelhubOriginalMaterial = a.material);
|
|
189
|
+
}), n;
|
|
190
|
+
}
|
|
191
|
+
function ge(e) {
|
|
192
|
+
const t = {};
|
|
193
|
+
for (const [i, n] of e) t[i] = n.length;
|
|
194
|
+
return t;
|
|
195
|
+
}
|
|
196
|
+
function q(e, t, i) {
|
|
197
|
+
for (const n of e.get(t) ?? []) n.visible = i;
|
|
198
|
+
}
|
|
199
|
+
function xe(e, t) {
|
|
200
|
+
for (const i of e.keys())
|
|
201
|
+
q(e, i, !t.has(i));
|
|
202
|
+
}
|
|
203
|
+
function be(e, t, i) {
|
|
204
|
+
for (const n of e.get(t) ?? []) n.material = i;
|
|
205
|
+
}
|
|
206
|
+
function ye(e, t) {
|
|
207
|
+
for (const i of e.get(t) ?? []) {
|
|
208
|
+
const n = i.userData.modelhubOriginalMaterial;
|
|
209
|
+
n && (i.material = n);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const K = "#2196f3", we = "#ff5722";
|
|
213
|
+
function E(e, t = {}) {
|
|
214
|
+
const {
|
|
215
|
+
fill: i = K,
|
|
216
|
+
text: n = "#ffffff",
|
|
217
|
+
textStroke: o = "rgba(0, 0, 0, 0.45)"
|
|
218
|
+
} = t, r = 256, s = document.createElement("canvas");
|
|
219
|
+
s.width = r, s.height = r;
|
|
220
|
+
const c = s.getContext("2d");
|
|
221
|
+
return c && (c.beginPath(), c.arc(r / 2, r / 2, r / 2 - 4, 0, Math.PI * 2), c.fillStyle = i, c.fill(), c.strokeStyle = "#ffffff", c.lineWidth = 8, c.stroke(), c.fillStyle = n, c.strokeStyle = o, c.lineWidth = 10, c.font = "bold 132px Arial, sans-serif", c.textAlign = "center", c.textBaseline = "middle", c.strokeText(String(e), r / 2, r / 2), c.fillText(String(e), r / 2, r / 2)), s;
|
|
222
|
+
}
|
|
223
|
+
function X(e, t = 0.2, i) {
|
|
224
|
+
const n = new _({
|
|
225
|
+
map: new N(E(e, i)),
|
|
226
|
+
transparent: !0,
|
|
227
|
+
depthTest: !1
|
|
228
|
+
}), o = new D(n);
|
|
229
|
+
return o.scale.set(t, t, 1), o.renderOrder = 999, o;
|
|
230
|
+
}
|
|
231
|
+
function Me(e, t, i) {
|
|
232
|
+
var o;
|
|
233
|
+
const n = e.material;
|
|
234
|
+
(o = n.map) == null || o.dispose(), n.map = new N(E(t, { fill: i })), n.needsUpdate = !0;
|
|
235
|
+
}
|
|
236
|
+
function Ie(e, t, i = 0.2, n) {
|
|
237
|
+
return t.map((o, r) => {
|
|
238
|
+
const s = X(r + 1, i, n);
|
|
239
|
+
return s.position.set(...o.position), s.name = `modelhub_marker_${o.annotation.id}`, s.userData.annotationId = o.annotation.id, s.userData.displayNumber = r + 1, e.add(s), s;
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
function Te(e) {
|
|
243
|
+
const { camera: t, model: i, anchors: n, width: o, height: r, isOccluded: s } = e, c = e.referenceDistance ?? 1, u = new h(), a = {};
|
|
244
|
+
i.updateMatrixWorld();
|
|
245
|
+
for (const l of n) {
|
|
246
|
+
u.set(...l.position).applyMatrix4(i.matrixWorld);
|
|
247
|
+
const d = u.distanceTo(t.position), f = s ? s(u, d) : !1, m = u.clone().project(t);
|
|
248
|
+
a[l.annotation.id] = {
|
|
249
|
+
x: (m.x * 0.5 + 0.5) * o,
|
|
250
|
+
y: (-m.y * 0.5 + 0.5) * r,
|
|
251
|
+
scale: Math.min(
|
|
252
|
+
1.6,
|
|
253
|
+
Math.max(0.55, c / Math.max(d, 1e-3))
|
|
254
|
+
),
|
|
255
|
+
zIndex: Math.max(1, Math.round((1 - m.z) * 1e5)) + (f ? 0 : 2e5),
|
|
256
|
+
inView: m.z < 1 && m.x > -1.05 && m.x < 1.05 && m.y > -1.05 && m.y < 1.05,
|
|
257
|
+
occluded: f
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
return a;
|
|
261
|
+
}
|
|
262
|
+
const j = 0.03, Z = {
|
|
263
|
+
enabled: !1,
|
|
264
|
+
isOccluded: () => !1,
|
|
265
|
+
dispose: () => {
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
async function $e(e) {
|
|
269
|
+
var r;
|
|
270
|
+
let t = [], i = [];
|
|
271
|
+
if (e.traverse((s) => {
|
|
272
|
+
if (!s.isMesh) return;
|
|
273
|
+
const c = s.geometry;
|
|
274
|
+
c != null && c.computeBoundsTree && (t.push(c), i.push(s));
|
|
275
|
+
}), !t.length) return Z;
|
|
276
|
+
for (const s of t)
|
|
277
|
+
s.boundsTree || (r = s.computeBoundsTree) == null || r.call(s, { maxLeafTris: 8 });
|
|
278
|
+
const n = new C();
|
|
279
|
+
n.firstHitOnly = !0;
|
|
280
|
+
const o = new h();
|
|
281
|
+
return {
|
|
282
|
+
enabled: !0,
|
|
283
|
+
isOccluded(s, c, u) {
|
|
284
|
+
return o.subVectors(c, s.position).normalize(), n.set(s.position, o), n.near = 0, n.far = Math.max(0, u - j), n.camera = s, n.intersectObjects(i, !1).length > 0;
|
|
285
|
+
},
|
|
286
|
+
dispose() {
|
|
287
|
+
var s;
|
|
288
|
+
for (const c of t) (s = c.disposeBoundsTree) == null || s.call(c);
|
|
289
|
+
t = [], i = [];
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
const y = {
|
|
294
|
+
headlight: {
|
|
295
|
+
ambient: 0.7,
|
|
296
|
+
lights: [
|
|
297
|
+
{ color: [1, 1, 1], intensity: 0.9, position: [4, 8, 6] },
|
|
298
|
+
{
|
|
299
|
+
color: [1, 1, 1],
|
|
300
|
+
intensity: 2.4,
|
|
301
|
+
position: [0, 0, 0],
|
|
302
|
+
attach: !0,
|
|
303
|
+
spot: !0
|
|
304
|
+
}
|
|
305
|
+
]
|
|
306
|
+
},
|
|
307
|
+
studio: {
|
|
308
|
+
ambient: 0.85,
|
|
309
|
+
lights: [
|
|
310
|
+
{ color: [0.78, 0.8, 1], intensity: 1.45, position: [1.35, 6.75, -7.26] },
|
|
311
|
+
{ color: [1, 0.88, 0.71], intensity: 1.5, position: [-5.71, 3.5, 7.43] },
|
|
312
|
+
{ color: [0.42, 0.74, 1], intensity: 0.25, position: [7.23, 6.78, 1.29] }
|
|
313
|
+
]
|
|
314
|
+
},
|
|
315
|
+
"dual-cam": {
|
|
316
|
+
ambient: 0.6,
|
|
317
|
+
lights: [
|
|
318
|
+
{
|
|
319
|
+
color: [0.6, 0.48, 0.3],
|
|
320
|
+
intensity: 1.6,
|
|
321
|
+
position: [-1, 0, 1],
|
|
322
|
+
attach: !0
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
color: [0.42, 0.42, 0.6],
|
|
326
|
+
intensity: 1.6,
|
|
327
|
+
position: [1, 0, 1],
|
|
328
|
+
attach: !0
|
|
329
|
+
}
|
|
330
|
+
]
|
|
331
|
+
},
|
|
332
|
+
"studio-cam": {
|
|
333
|
+
ambient: 0.7,
|
|
334
|
+
lights: [
|
|
335
|
+
{
|
|
336
|
+
color: [0.78, 0.8, 1],
|
|
337
|
+
intensity: 1.35,
|
|
338
|
+
position: [-1, 0.3, 1],
|
|
339
|
+
attach: !0
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
color: [1, 0.88, 0.71],
|
|
343
|
+
intensity: 1.4,
|
|
344
|
+
position: [1, 0.3, 1],
|
|
345
|
+
attach: !0
|
|
346
|
+
},
|
|
347
|
+
{ color: [0.42, 0.74, 1], intensity: 0.25, position: [7.23, 6.78, 1.29] }
|
|
348
|
+
]
|
|
349
|
+
}
|
|
350
|
+
}, Q = "headlight";
|
|
351
|
+
function Ae() {
|
|
352
|
+
return Object.keys(y);
|
|
353
|
+
}
|
|
354
|
+
function Y(e) {
|
|
355
|
+
var t;
|
|
356
|
+
(t = e.parent) == null || t.remove(e);
|
|
357
|
+
}
|
|
358
|
+
function Se(e) {
|
|
359
|
+
var c;
|
|
360
|
+
const { scene: t, camera: i, exposure: n = 1 } = e;
|
|
361
|
+
(c = e.previous) == null || c.remove();
|
|
362
|
+
const o = y[e.preset ?? ""] ?? y[Q], r = [], s = new L(16777215, o.ambient * n);
|
|
363
|
+
t.add(s), r.push(s);
|
|
364
|
+
for (const u of o.lights) {
|
|
365
|
+
let a;
|
|
366
|
+
if (u.spot) {
|
|
367
|
+
const d = new U(new I(...u.color));
|
|
368
|
+
d.angle = 0.5, d.penumbra = 0.9, d.decay = 0, a = d;
|
|
369
|
+
} else
|
|
370
|
+
a = new F(new I(...u.color));
|
|
371
|
+
a.intensity = u.intensity, a.position.set(...u.position);
|
|
372
|
+
const l = a.target;
|
|
373
|
+
u.attach ? (l == null || l.position.set(0, 0, u.spot ? -1 : 0), i.add(a), l && i.add(l)) : (t.add(a), l && t.add(l)), r.push(a), l && r.push(l);
|
|
374
|
+
}
|
|
375
|
+
return {
|
|
376
|
+
lights: r,
|
|
377
|
+
remove() {
|
|
378
|
+
for (const u of r) Y(u);
|
|
379
|
+
r.length = 0;
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
const b = [
|
|
384
|
+
{ id: "white", label: "White", hex: "#ffffff", light: !0 },
|
|
385
|
+
{ id: "light-grey", label: "Light grey", hex: "#d4d4d4", light: !0 },
|
|
386
|
+
{ id: "medium-grey", label: "Medium grey", hex: "#808080", light: !1 },
|
|
387
|
+
{ id: "dark-grey", label: "Dark grey", hex: "#252525", light: !1 },
|
|
388
|
+
{ id: "black", label: "Black", hex: "#000000", light: !1 }
|
|
389
|
+
], J = "dark-grey";
|
|
390
|
+
function ee(e) {
|
|
391
|
+
if (typeof e != "string") return "";
|
|
392
|
+
const t = e.trim().replace(/^#/, "");
|
|
393
|
+
return /^[0-9a-f]{6}$/i.test(t) ? `#${t.toLowerCase()}` : /^[0-9a-f]{3}$/i.test(t) ? `#${[...t].map((i) => i + i).join("").toLowerCase()}` : "";
|
|
394
|
+
}
|
|
395
|
+
function te(e) {
|
|
396
|
+
const t = Number.parseInt(e.replace("#", ""), 16);
|
|
397
|
+
return Number.isFinite(t) ? (0.299 * (t >> 16 & 255) + 0.587 * (t >> 8 & 255) + 0.114 * (t & 255)) / 255 : 0;
|
|
398
|
+
}
|
|
399
|
+
function Ne(e, t) {
|
|
400
|
+
const i = ee(t);
|
|
401
|
+
return i ? {
|
|
402
|
+
id: "custom",
|
|
403
|
+
label: "Custom",
|
|
404
|
+
hex: i,
|
|
405
|
+
light: te(i) > 0.5
|
|
406
|
+
} : b.find((n) => n.id === e) ?? b.find((n) => n.id === J) ?? b[0];
|
|
407
|
+
}
|
|
408
|
+
const ne = 2;
|
|
409
|
+
function $(e) {
|
|
410
|
+
e.updateMatrixWorld(!0);
|
|
411
|
+
const t = new g(), i = new g();
|
|
412
|
+
return e.traverse((n) => {
|
|
413
|
+
const o = n, r = n;
|
|
414
|
+
o.isSkinnedMesh ? (o.computeBoundingBox(), o.boundingBox && (i.copy(o.boundingBox).applyMatrix4(o.matrixWorld), t.union(i))) : r.isMesh && r.geometry && (r.geometry.boundingBox || r.geometry.computeBoundingBox(), r.geometry.boundingBox && (i.copy(r.geometry.boundingBox).applyMatrix4(r.matrixWorld), t.union(i)));
|
|
415
|
+
}), t.isEmpty() ? new g().setFromObject(e) : t;
|
|
416
|
+
}
|
|
417
|
+
function ve(e, t = ne) {
|
|
418
|
+
const n = $(e).getSize(new h()), o = Math.max(n.x, n.y, n.z) || 1, r = t / o;
|
|
419
|
+
e.scale.multiplyScalar(r);
|
|
420
|
+
const s = $(e);
|
|
421
|
+
return e.position.sub(s.getCenter(new h())), r;
|
|
422
|
+
}
|
|
423
|
+
function Oe(e, t = 1.3) {
|
|
424
|
+
return t / Math.tan(e * Math.PI / 360);
|
|
425
|
+
}
|
|
426
|
+
const ie = 0.5, oe = 1.5;
|
|
427
|
+
function ke(e, t, i = {}) {
|
|
428
|
+
t.updateMatrixWorld();
|
|
429
|
+
const n = new h(...e.position).applyMatrix4(
|
|
430
|
+
t.matrixWorld
|
|
431
|
+
);
|
|
432
|
+
if (e.camera) {
|
|
433
|
+
const u = new h(...e.camera.eye).applyMatrix4(
|
|
434
|
+
t.matrixWorld
|
|
435
|
+
), l = new h(...e.camera.target).applyMatrix4(
|
|
436
|
+
t.matrixWorld
|
|
437
|
+
).clone().sub(u);
|
|
438
|
+
l.lengthSq() < 1e-10 && l.copy(n).sub(u), l.normalize();
|
|
439
|
+
const d = Math.max(n.distanceTo(u), 0.2);
|
|
440
|
+
return {
|
|
441
|
+
position: u,
|
|
442
|
+
target: u.clone().addScaledVector(l, d),
|
|
443
|
+
...e.camera.fov ? { fov: e.camera.fov } : {}
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
const o = i.outward ?? !0, r = new h(n.x, 0, n.z);
|
|
447
|
+
r.lengthSq() < 1e-8 && r.set(0, 0, 1), r.normalize();
|
|
448
|
+
const s = o ? -(i.outwardDistance ?? ie) : i.inwardDistance ?? oe, c = n.clone().addScaledVector(r, s);
|
|
449
|
+
return c.y = n.y, { position: c, target: n };
|
|
450
|
+
}
|
|
451
|
+
const re = { cancel: () => {
|
|
452
|
+
} };
|
|
453
|
+
function Ee(e) {
|
|
454
|
+
const { camera: t, controls: i, to: n, onFrame: o, onDone: r } = e, s = () => {
|
|
455
|
+
o == null || o(), r == null || r();
|
|
456
|
+
};
|
|
457
|
+
if (e.reducedMotion)
|
|
458
|
+
return t.position.copy(n.position), i.target.copy(n.target), i.update(), s(), re;
|
|
459
|
+
const c = e.durationMs ?? 900, u = t.position.clone(), a = i.target.clone(), l = performance.now();
|
|
460
|
+
let d = null;
|
|
461
|
+
const f = (m) => {
|
|
462
|
+
const p = Math.min((m - l) / c, 1), M = p * (2 - p);
|
|
463
|
+
t.position.lerpVectors(u, n.position, M), i.target.lerpVectors(a, n.target, M), i.update(), o == null || o(), p < 1 ? d = requestAnimationFrame(f) : (d = null, r == null || r());
|
|
464
|
+
};
|
|
465
|
+
return d = requestAnimationFrame(f), {
|
|
466
|
+
cancel() {
|
|
467
|
+
d !== null && cancelAnimationFrame(d), d = null;
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
function Be(e, t, i) {
|
|
472
|
+
const n = t || i;
|
|
473
|
+
e.fov !== n && (e.fov = n, e.updateProjectionMatrix());
|
|
474
|
+
}
|
|
475
|
+
const se = 500;
|
|
476
|
+
function Re(e) {
|
|
477
|
+
var o, r, s;
|
|
478
|
+
if (!e)
|
|
479
|
+
return "Model loaded, but it carries no educational metadata.";
|
|
480
|
+
const t = [];
|
|
481
|
+
(o = e.model) != null && o.title && t.push(`${e.model.title} loaded`), (r = e.model) != null && r.altText && t.push(e.model.altText);
|
|
482
|
+
const i = ((s = e.regions) == null ? void 0 : s.length) ?? 0, n = w(e).length;
|
|
483
|
+
return i && t.push(`${i} labelled ${i === 1 ? "region" : "regions"}`), n && t.push(
|
|
484
|
+
`${n} ${n === 1 ? "annotation" : "annotations"}`
|
|
485
|
+
), t.map((c) => c.replace(/\.$/, "")).join(". ") + ".";
|
|
486
|
+
}
|
|
487
|
+
function _e(e, t, i) {
|
|
488
|
+
return `Annotation ${t + 1} of ${i}: ${e || "Untitled"}.`;
|
|
489
|
+
}
|
|
490
|
+
function De(e, t = se, i = () => Date.now()) {
|
|
491
|
+
let n = -1 / 0, o = null, r = null;
|
|
492
|
+
const s = (a) => {
|
|
493
|
+
n = i(), o = null, e(a);
|
|
494
|
+
};
|
|
495
|
+
return { announce: (a) => {
|
|
496
|
+
const l = i() - n;
|
|
497
|
+
if (l >= t) {
|
|
498
|
+
s(a);
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
o = a, r || (r = setTimeout(() => {
|
|
502
|
+
r = null, o !== null && s(o);
|
|
503
|
+
}, t - l));
|
|
504
|
+
}, flush: () => {
|
|
505
|
+
r && (clearTimeout(r), r = null), o !== null && s(o);
|
|
506
|
+
} };
|
|
507
|
+
}
|
|
508
|
+
function A(e, t) {
|
|
509
|
+
return `## ${e}
|
|
510
|
+
${t}
|
|
511
|
+
|
|
512
|
+
`;
|
|
513
|
+
}
|
|
514
|
+
function Ce(e, t = {}) {
|
|
515
|
+
const i = e.model ?? {}, n = k(e), o = w(e);
|
|
516
|
+
let r = `# ${i.title || "Untitled model"}
|
|
517
|
+
|
|
518
|
+
`;
|
|
519
|
+
if (i.altText && (r += A(t.summaryHeading ?? "Summary", i.altText)), i.longDescription && (r += A("Full description", i.longDescription)), n.length) {
|
|
520
|
+
r += `## Regions (${n.length})
|
|
521
|
+
|
|
522
|
+
`;
|
|
523
|
+
for (const s of n)
|
|
524
|
+
r += `### ${T(s)}
|
|
525
|
+
${s.description || "No description."}
|
|
526
|
+
|
|
527
|
+
`;
|
|
528
|
+
}
|
|
529
|
+
return o.length && (r += `## Annotations (${o.length})
|
|
530
|
+
|
|
531
|
+
`, o.forEach((s, c) => {
|
|
532
|
+
const u = s.regionId ? ` (${T(n.find((l) => l.id === s.regionId))})` : "", a = `${c + 1}. ${s.title || "Untitled"}${u}`;
|
|
533
|
+
r += `### ${a}
|
|
534
|
+
${s.body || "No description."}
|
|
535
|
+
|
|
536
|
+
`;
|
|
537
|
+
})), r += `## Licensing
|
|
538
|
+
`, r += `- **License:** ${i.license || "Not specified"}
|
|
539
|
+
`, r += `- **Attribution:** ${i.attribution || "Not specified"}
|
|
540
|
+
`, r += `- **Creator:** ${i.creator || "Not specified"}
|
|
541
|
+
`, r += `- **Source:** ${i.source || "Not specified"}
|
|
542
|
+
`, r;
|
|
543
|
+
}
|
|
544
|
+
function Le(e) {
|
|
545
|
+
var i;
|
|
546
|
+
return `${(((i = e.model) == null ? void 0 : i.title) || "model").trim().replace(/[^\w\-. ]+/g, "").replace(/\s+/g, "_") || "model"}_transcript.md`;
|
|
547
|
+
}
|
|
548
|
+
function Ue(e) {
|
|
549
|
+
e && e.traverse((t) => {
|
|
550
|
+
var r, s;
|
|
551
|
+
const i = t, n = i.geometry;
|
|
552
|
+
n && (n.boundsTree && n.disposeBoundsTree && n.disposeBoundsTree(), (r = n.dispose) == null || r.call(n));
|
|
553
|
+
const o = [].concat(
|
|
554
|
+
i.material ?? []
|
|
555
|
+
);
|
|
556
|
+
for (const c of o)
|
|
557
|
+
if (c) {
|
|
558
|
+
for (const u of Object.values(c)) {
|
|
559
|
+
const a = u;
|
|
560
|
+
a && a.isTexture && a.dispose();
|
|
561
|
+
}
|
|
562
|
+
(s = c.dispose) == null || s.call(c);
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
}
|
|
566
|
+
export {
|
|
567
|
+
b as BACKGROUND_OPTIONS,
|
|
568
|
+
se as CAMERA_ANNOUNCE_INTERVAL,
|
|
569
|
+
J as DEFAULT_BACKGROUND,
|
|
570
|
+
Q as DEFAULT_LIGHTING,
|
|
571
|
+
ze as GlbParseError,
|
|
572
|
+
y as LIGHTING_PRESETS,
|
|
573
|
+
K as MARKER_FILL,
|
|
574
|
+
we as MARKER_FILL_ACTIVE,
|
|
575
|
+
ne as NORMALIZED_SIZE,
|
|
576
|
+
j as OCCLUSION_BIAS,
|
|
577
|
+
oe as TOUR_DISTANCE_INWARD,
|
|
578
|
+
ie as TOUR_DISTANCE_OUTWARD,
|
|
579
|
+
_e as annotationSummary,
|
|
580
|
+
w as annotationsInReadingOrder,
|
|
581
|
+
Be as applyFov,
|
|
582
|
+
Se as applyLighting,
|
|
583
|
+
xe as applyVisibilityDefaults,
|
|
584
|
+
he as buildRegionMeshMap,
|
|
585
|
+
Ce as buildTranscript,
|
|
586
|
+
ye as clearRegionHighlight,
|
|
587
|
+
$ as computeDisplayBox,
|
|
588
|
+
ke as computeTourPlacement,
|
|
589
|
+
De as createAnnouncer,
|
|
590
|
+
X as createMarkerSprite,
|
|
591
|
+
Ie as createMarkerSprites,
|
|
592
|
+
$e as createOcclusionIndex,
|
|
593
|
+
Ue as disposeSceneGraph,
|
|
594
|
+
Oe as distanceForFov,
|
|
595
|
+
E as drawMarkerCanvas,
|
|
596
|
+
W as errorsOf,
|
|
597
|
+
P as fetchBytes,
|
|
598
|
+
Ee as flyCamera,
|
|
599
|
+
ve as frameModel,
|
|
600
|
+
fe as fromViewerPosition,
|
|
601
|
+
be as highlightRegion,
|
|
602
|
+
v as isVec3,
|
|
603
|
+
Ae as lightingPresetNames,
|
|
604
|
+
ue as loadModel,
|
|
605
|
+
de as logFindings,
|
|
606
|
+
te as luminance,
|
|
607
|
+
Re as modelSummary,
|
|
608
|
+
O as needsConversion,
|
|
609
|
+
V as normalizeAnnotationIds,
|
|
610
|
+
ee as normalizeHexColor,
|
|
611
|
+
pe as normalizeModel,
|
|
612
|
+
me as parseBodySegments,
|
|
613
|
+
Te as projectMarkers,
|
|
614
|
+
G as readEmbedded,
|
|
615
|
+
ge as regionBindingCounts,
|
|
616
|
+
T as regionLabel,
|
|
617
|
+
k as regionsInOrder,
|
|
618
|
+
Ne as resolveBackground,
|
|
619
|
+
le as resourcePathFor,
|
|
620
|
+
Me as setMarkerSpriteColor,
|
|
621
|
+
q as setRegionVisible,
|
|
622
|
+
z as sidecarUrlFor,
|
|
623
|
+
x as toViewerPosition,
|
|
624
|
+
Le as transcriptFileName
|
|
625
|
+
};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scene lighting.
|
|
3
|
+
*
|
|
4
|
+
* Lighting is not decoration here. These rigs were measured from this
|
|
5
|
+
* organisation's Sketchfab viewers through the Viewer API, so a model that
|
|
6
|
+
* was tuned on Sketchfab looks the same after migration. Keeping them in one
|
|
7
|
+
* place means a model lit correctly in one viewer is lit correctly in both.
|
|
8
|
+
*
|
|
9
|
+
* `attach: true` parents a light to the camera, so its direction stays fixed
|
|
10
|
+
* relative to whatever the viewer is looking at rather than to the world.
|
|
11
|
+
*/
|
|
12
|
+
import type { Camera, Object3D, Scene } from "three";
|
|
13
|
+
/** One light in a preset. */
|
|
14
|
+
export interface LightSpec {
|
|
15
|
+
/** Linear RGB, each 0 to 1. */
|
|
16
|
+
color: [number, number, number];
|
|
17
|
+
intensity: number;
|
|
18
|
+
position: [number, number, number];
|
|
19
|
+
/** Parent to the camera instead of the scene. */
|
|
20
|
+
attach?: boolean;
|
|
21
|
+
/** Render as a wide, feathered spot rather than a directional light. */
|
|
22
|
+
spot?: boolean;
|
|
23
|
+
}
|
|
24
|
+
/** A named lighting rig. */
|
|
25
|
+
export interface LightingPreset {
|
|
26
|
+
ambient: number;
|
|
27
|
+
lights: LightSpec[];
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* The rigs. `headlight` is the plain single-camera light; the other three
|
|
31
|
+
* reproduce Sketchfab rigs captured from live models.
|
|
32
|
+
*
|
|
33
|
+
* Sketchfab pairs its rigs with an HDRI environment (lightIntensity 3), so
|
|
34
|
+
* the intensities here are scaled up from the captured values to compensate
|
|
35
|
+
* for approximating that environment with flat ambient light.
|
|
36
|
+
*/
|
|
37
|
+
export declare const LIGHTING_PRESETS: Record<string, LightingPreset>;
|
|
38
|
+
/** The preset used when a name is missing or unknown. */
|
|
39
|
+
export declare const DEFAULT_LIGHTING = "headlight";
|
|
40
|
+
/** Every preset name, for building a picker. */
|
|
41
|
+
export declare function lightingPresetNames(): string[];
|
|
42
|
+
/** The lights a call to {@link applyLighting} created, so they can be removed. */
|
|
43
|
+
export interface LightingHandle {
|
|
44
|
+
lights: Object3D[];
|
|
45
|
+
/** Detach every light this handle owns from whatever it was added to. */
|
|
46
|
+
remove(): void;
|
|
47
|
+
}
|
|
48
|
+
/** Options for {@link applyLighting}. */
|
|
49
|
+
export interface LightingOptions {
|
|
50
|
+
scene: Scene;
|
|
51
|
+
camera: Camera;
|
|
52
|
+
/** Preset name; an unknown one falls back to `headlight`. */
|
|
53
|
+
preset?: string;
|
|
54
|
+
/** Multiplier on the preset's ambient level, mirroring Sketchfab exposure. */
|
|
55
|
+
exposure?: number;
|
|
56
|
+
/** A previous handle to tear down first. */
|
|
57
|
+
previous?: LightingHandle | null;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Build a preset's lights and add them to the scene, removing any previous
|
|
61
|
+
* set first. Returns a handle the caller keeps so the next call, or teardown,
|
|
62
|
+
* can take them out again.
|
|
63
|
+
*/
|
|
64
|
+
export declare function applyLighting(options: LightingOptions): LightingHandle;
|