@nebula-spatial/viewer 0.3.0 → 0.4.1

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.
@@ -0,0 +1,421 @@
1
+ var v = Object.defineProperty;
2
+ var y = (c, e, t) => e in c ? v(c, e, { enumerable: !0, configurable: !0, writable: !0, value: t }) : c[e] = t;
3
+ var a = (c, e, t) => y(c, typeof e != "symbol" ? e + "" : e, t);
4
+ import * as o from "three";
5
+ import { V as p } from "./index-D0lW4RQP.js";
6
+ const x = 1, b = (
7
+ /* glsl */
8
+ `
9
+ uniform mat4 textureMatrix;
10
+ varying vec4 vReflectionUv;
11
+ varying vec3 vWorldPosition;
12
+
13
+ void main() {
14
+ vec4 worldPosition = modelMatrix * vec4(position, 1.0);
15
+ vWorldPosition = worldPosition.xyz;
16
+ vReflectionUv = textureMatrix * vec4(position, 1.0);
17
+ // Match Three.js built-in materials: transform to view space before projection.
18
+ vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
19
+ gl_Position = projectionMatrix * mvPosition;
20
+ }
21
+ `
22
+ ), M = (
23
+ /* glsl */
24
+ `
25
+ uniform sampler2D tDiffuse;
26
+ uniform vec2 resolution;
27
+ uniform vec3 fineGridColor;
28
+ uniform vec3 coarseGridColor;
29
+ uniform float blurIntensity;
30
+ uniform float reflectionMix;
31
+ uniform float gridOpacity;
32
+ uniform float gridScale;
33
+ uniform vec2 groundCenter;
34
+ uniform float maxRadius;
35
+ uniform float fadeSoftness;
36
+
37
+ varying vec4 vReflectionUv;
38
+ varying vec3 vWorldPosition;
39
+
40
+ vec4 blur9(sampler2D image, vec2 uv, vec2 res, float radius) {
41
+ vec4 color = vec4(0.0);
42
+ vec2 stepSize = radius / res;
43
+ color += texture2D(image, uv + vec2(-stepSize.x, -stepSize.y)) * 0.0625;
44
+ color += texture2D(image, uv + vec2(0.0, -stepSize.y)) * 0.125;
45
+ color += texture2D(image, uv + vec2(stepSize.x, -stepSize.y)) * 0.0625;
46
+ color += texture2D(image, uv + vec2(-stepSize.x, 0.0)) * 0.125;
47
+ color += texture2D(image, uv) * 0.25;
48
+ color += texture2D(image, uv + vec2(stepSize.x, 0.0)) * 0.125;
49
+ color += texture2D(image, uv + vec2(-stepSize.x, stepSize.y)) * 0.0625;
50
+ color += texture2D(image, uv + vec2(0.0, stepSize.y)) * 0.125;
51
+ color += texture2D(image, uv + vec2(stepSize.x, stepSize.y)) * 0.0625;
52
+ return color;
53
+ }
54
+
55
+ float gridLine(vec2 position, float scale) {
56
+ vec2 coordinate = position / scale;
57
+ vec2 width = max(fwidth(coordinate), vec2(0.0001));
58
+ vec2 distanceToLine = abs(fract(coordinate - 0.5) - 0.5) / width;
59
+ return 1.0 - min(min(distanceToLine.x, distanceToLine.y), 1.0);
60
+ }
61
+
62
+ void main() {
63
+ vec2 groundPosition = vWorldPosition.xy;
64
+ float edgeDistance = length(groundPosition - groundCenter);
65
+ float edgeFade = 1.0 - smoothstep(maxRadius - fadeSoftness, maxRadius, edgeDistance);
66
+
67
+ float fineLine = gridLine(groundPosition, gridScale);
68
+ float coarseLine = gridLine(groundPosition, gridScale * 10.0);
69
+ float gridAlpha = max(fineLine * 0.16, coarseLine * 0.42) * gridOpacity * edgeFade;
70
+ vec3 gridColor = mix(fineGridColor, coarseGridColor, coarseLine);
71
+
72
+ vec2 reflectionUv = vReflectionUv.xy / vReflectionUv.w;
73
+ bool validReflection = reflectionUv.x >= 0.0 && reflectionUv.x <= 1.0
74
+ && reflectionUv.y >= 0.0 && reflectionUv.y <= 1.0;
75
+ vec4 reflection = validReflection
76
+ ? blur9(tDiffuse, reflectionUv, resolution, blurIntensity)
77
+ : vec4(0.0);
78
+ float reflectionAlpha = reflectionMix * edgeFade * (validReflection ? 1.0 : 0.0);
79
+
80
+ float alpha = reflectionAlpha + gridAlpha * (1.0 - reflectionAlpha);
81
+ if (alpha <= 0.001) discard;
82
+ vec3 color = mix(reflection.rgb, gridColor, gridAlpha / max(alpha, 0.001));
83
+ gl_FragColor = vec4(color, alpha);
84
+ #include <tonemapping_fragment>
85
+ #include <colorspace_fragment>
86
+ }
87
+ `
88
+ );
89
+ class w extends o.Mesh {
90
+ constructor({ size: t = 20, textureWidth: i = 512, textureHeight: r = 512 } = {}) {
91
+ const s = new o.Matrix4(), n = new o.WebGLRenderTarget(i, r, {
92
+ type: o.HalfFloatType,
93
+ minFilter: o.LinearFilter,
94
+ magFilter: o.LinearFilter,
95
+ format: o.RGBAFormat,
96
+ depthBuffer: !0,
97
+ samples: 0
98
+ }), l = new o.ShaderMaterial({
99
+ uniforms: {
100
+ tDiffuse: { value: n.texture },
101
+ textureMatrix: { value: s },
102
+ resolution: { value: new o.Vector2(i, r) },
103
+ fineGridColor: { value: new o.Color(2502702) },
104
+ coarseGridColor: { value: new o.Color(7897726) },
105
+ blurIntensity: { value: 1.7 },
106
+ reflectionMix: { value: 0.07 },
107
+ gridOpacity: { value: 1 },
108
+ gridScale: { value: 0.2 },
109
+ groundCenter: { value: new o.Vector2() },
110
+ maxRadius: { value: t * 0.5 },
111
+ fadeSoftness: { value: t * 0.325 }
112
+ },
113
+ vertexShader: b,
114
+ fragmentShader: M,
115
+ transparent: !0,
116
+ depthWrite: !1,
117
+ toneMapped: !0,
118
+ fog: !1
119
+ });
120
+ super(new o.PlaneGeometry(t, t), l);
121
+ a(this, "renderTarget");
122
+ a(this, "textureMatrix");
123
+ a(this, "virtualCamera");
124
+ a(this, "clipBias");
125
+ a(this, "reflectorPlane");
126
+ a(this, "normal");
127
+ a(this, "reflectorWorldPosition");
128
+ a(this, "cameraWorldPosition");
129
+ a(this, "rotationMatrix");
130
+ a(this, "lookAtPosition");
131
+ a(this, "clipPlane");
132
+ a(this, "view");
133
+ a(this, "target");
134
+ a(this, "q");
135
+ this.renderTarget = n, this.textureMatrix = s, this.virtualCamera = new o.PerspectiveCamera(), this.virtualCamera.layers.set(x), this.clipBias = 3e-3, this.frustumCulled = !1, this.renderOrder = 1, this.position.z = 2e-3, this.reflectorPlane = new o.Plane(), this.normal = new o.Vector3(), this.reflectorWorldPosition = new o.Vector3(), this.cameraWorldPosition = new o.Vector3(), this.rotationMatrix = new o.Matrix4(), this.lookAtPosition = new o.Vector3(0, 0, -1), this.clipPlane = new o.Vector4(), this.view = new o.Vector3(), this.target = new o.Vector3(), this.q = new o.Vector4();
136
+ }
137
+ renderReflection(t, i, r) {
138
+ if (this.reflectorWorldPosition.setFromMatrixPosition(this.matrixWorld), this.cameraWorldPosition.setFromMatrixPosition(r.matrixWorld), this.rotationMatrix.extractRotation(this.matrixWorld), this.normal.set(0, 0, 1).applyMatrix4(this.rotationMatrix), this.view.subVectors(this.reflectorWorldPosition, this.cameraWorldPosition), this.view.dot(this.normal) > 0) return;
139
+ this.view.reflect(this.normal).negate().add(this.reflectorWorldPosition), this.rotationMatrix.extractRotation(r.matrixWorld), this.lookAtPosition.set(0, 0, -1).applyMatrix4(this.rotationMatrix).add(this.cameraWorldPosition), this.target.subVectors(this.reflectorWorldPosition, this.lookAtPosition).reflect(this.normal).negate().add(this.reflectorWorldPosition), this.virtualCamera.position.copy(this.view), this.virtualCamera.up.set(0, 1, 0).applyMatrix4(this.rotationMatrix).reflect(this.normal), this.virtualCamera.lookAt(this.target), this.virtualCamera.near = r.near, this.virtualCamera.far = r.far, this.virtualCamera.updateMatrixWorld(), this.virtualCamera.projectionMatrix.copy(r.projectionMatrix), this.textureMatrix.set(
140
+ 0.5,
141
+ 0,
142
+ 0,
143
+ 0.5,
144
+ 0,
145
+ 0.5,
146
+ 0,
147
+ 0.5,
148
+ 0,
149
+ 0,
150
+ 0.5,
151
+ 0.5,
152
+ 0,
153
+ 0,
154
+ 0,
155
+ 1
156
+ ).multiply(this.virtualCamera.projectionMatrix).multiply(this.virtualCamera.matrixWorldInverse).multiply(this.matrixWorld), this.reflectorPlane.setFromNormalAndCoplanarPoint(this.normal, this.reflectorWorldPosition).applyMatrix4(this.virtualCamera.matrixWorldInverse), this.clipPlane.set(
157
+ this.reflectorPlane.normal.x,
158
+ this.reflectorPlane.normal.y,
159
+ this.reflectorPlane.normal.z,
160
+ this.reflectorPlane.constant
161
+ );
162
+ const s = this.virtualCamera.projectionMatrix;
163
+ this.q.set(
164
+ (Math.sign(this.clipPlane.x) + s.elements[8]) / s.elements[0],
165
+ (Math.sign(this.clipPlane.y) + s.elements[9]) / s.elements[5],
166
+ -1,
167
+ (1 + s.elements[10]) / s.elements[14]
168
+ ), this.clipPlane.multiplyScalar(2 / this.clipPlane.dot(this.q)), s.elements[2] = this.clipPlane.x, s.elements[6] = this.clipPlane.y, s.elements[10] = this.clipPlane.z + 1 - this.clipBias, s.elements[14] = this.clipPlane.w, this.virtualCamera.projectionMatrixInverse.copy(s).invert();
169
+ const n = t.getRenderTarget(), l = t.xr.enabled, u = t.shadowMap.autoUpdate, d = t.shadowMap.enabled;
170
+ try {
171
+ t.xr.enabled = !1, t.shadowMap.enabled = !1, t.shadowMap.autoUpdate = !1, t.setRenderTarget(this.renderTarget), t.state.buffers.depth.setMask(!0), t.clear(), t.render(i, this.virtualCamera);
172
+ } finally {
173
+ t.setRenderTarget(n), t.xr.enabled = l, t.shadowMap.enabled = d, t.shadowMap.autoUpdate = u;
174
+ }
175
+ }
176
+ setRenderSize(t, i) {
177
+ this.renderTarget.setSize(t, i), this.material.uniforms.resolution.value.set(t, i);
178
+ }
179
+ // Radius is in world units; the outer 65% fades before the square mesh edge.
180
+ setRadius(t, i, r = this.material.uniforms.groundCenter.value) {
181
+ this.scale.setScalar(t * 2 / this.geometry.parameters.width), this.position.x = r.x, this.position.y = r.y, this.material.uniforms.groundCenter.value.set(r.x, r.y), this.material.uniforms.gridScale.value = i, this.material.uniforms.maxRadius.value = t, this.material.uniforms.fadeSoftness.value = t * 0.65;
182
+ }
183
+ }
184
+ const g = {
185
+ light: {
186
+ background: p.light,
187
+ fineGrid: 5859168,
188
+ coarseGrid: 3427136,
189
+ gridOpacity: 0.85,
190
+ reflectionMix: 0.06
191
+ },
192
+ dark: {
193
+ background: p.dark,
194
+ fineGrid: 7375236,
195
+ coarseGrid: 5552554,
196
+ gridOpacity: 0.9,
197
+ reflectionMix: 0.06
198
+ }
199
+ }, P = {
200
+ simulation: 0.68,
201
+ model: 1.4
202
+ };
203
+ function C(c) {
204
+ const e = /* @__PURE__ */ new Set(), t = /* @__PURE__ */ new Set(), i = /* @__PURE__ */ new Set(), r = /* @__PURE__ */ new Set();
205
+ c.traverse((s) => {
206
+ var l;
207
+ const n = s;
208
+ n.geometry && e.add(n.geometry);
209
+ for (const u of Array.isArray(n.material) ? n.material : [n.material])
210
+ if (u) {
211
+ t.add(u);
212
+ for (const d of Object.values(u)) {
213
+ if (!S(d)) continue;
214
+ i.add(d);
215
+ const h = (l = d.source) == null ? void 0 : l.data;
216
+ h && typeof h.close == "function" && r.add(h);
217
+ }
218
+ }
219
+ });
220
+ for (const s of e) s.dispose();
221
+ for (const s of t) s.dispose();
222
+ for (const s of i) s.dispose();
223
+ for (const s of r) s.close();
224
+ }
225
+ function S(c) {
226
+ return !!(c && typeof c == "object" && c.isTexture);
227
+ }
228
+ const f = 2213822, G = 6000111, R = 6;
229
+ function L(c, e, t) {
230
+ e.updateMatrixWorld();
231
+ const i = new o.Vector3();
232
+ return e.getWorldDirection(i).normalize(), c.setFromNormalAndCoplanarPoint(i, t);
233
+ }
234
+ class F {
235
+ constructor(e, t = "dark", i, r = "simulation") {
236
+ a(this, "root", new o.Group());
237
+ a(this, "reflector", new w({ size: 100, textureWidth: 256, textureHeight: 256 }));
238
+ a(this, "values");
239
+ a(this, "fog");
240
+ a(this, "raycaster", new o.Raycaster());
241
+ a(this, "pointer", new o.Vector2());
242
+ a(this, "dragPlane", new o.Plane());
243
+ a(this, "dragTarget", new o.Vector3());
244
+ a(this, "dragIntersection", new o.Vector3());
245
+ a(this, "dragOffset", new o.Vector3());
246
+ a(this, "bodyGroups", /* @__PURE__ */ new Map());
247
+ a(this, "auxiliaries", /* @__PURE__ */ new Set());
248
+ a(this, "forceLine");
249
+ a(this, "forceBoosted", !1);
250
+ a(this, "grabCanvas", null);
251
+ a(this, "previousCursor", "");
252
+ a(this, "size", "");
253
+ a(this, "groundVisible");
254
+ a(this, "disposed", !1);
255
+ this.model = e, this.runtime = i, this.presentation = r, this.values = g[t];
256
+ const s = this.values, n = new o.Box3().setFromObject(e), l = Math.max(...n.getSize(new o.Vector3()).toArray());
257
+ if (!Number.isFinite(l) || l <= 0) throw new Error("Empty simulation geometry.");
258
+ this.root.add(e), this.forceLine = this.createForceLine(), this.root.add(this.forceLine), e.traverse((m) => {
259
+ m instanceof o.Mesh && m.layers.enable(x);
260
+ }), this.root.add(this.reflector);
261
+ const u = Math.max(n.getSize(new o.Vector3()).length() * 0.5, 0.02), d = this.reflector.material.uniforms;
262
+ d.fineGridColor.value.setHex(s.fineGrid), d.coarseGridColor.value.setHex(s.coarseGrid), d.gridOpacity.value = s.gridOpacity, d.reflectionMix.value = s.reflectionMix, this.layoutGround(n, u), this.groundVisible = r === "simulation", this.setGroundVisible(this.groundVisible);
263
+ const h = u / Math.sin(o.MathUtils.degToRad(38) / 2) * 1.25;
264
+ this.fog = new o.Fog(s.background, h * 2.5, h * 8);
265
+ }
266
+ setTheme(e) {
267
+ if (this.disposed) return;
268
+ const t = this.values = g[e], i = this.reflector.material.uniforms;
269
+ i.fineGridColor.value.setHex(t.fineGrid), i.coarseGridColor.value.setHex(t.coarseGrid), i.gridOpacity.value = t.gridOpacity, i.reflectionMix.value = t.reflectionMix, this.fog.color.setHex(t.background);
270
+ }
271
+ layoutGround(e, t) {
272
+ this.reflector.setRadius(t * 8, Math.pow(10, Math.floor(Math.log10(Math.max(t, 0.01)))), e.getCenter(new o.Vector3()));
273
+ }
274
+ /**
275
+ * Re-fits the ground disc and reflection to the model's current world bounds.
276
+ * Model previews call this after re-orienting the asset's authored up axis:
277
+ * an axis-aligned bounding box is not rotation invariant, so the floor that
278
+ * was sized to a wide-flat footprint can end up clipping an upright model.
279
+ */
280
+ reseatGround() {
281
+ if (this.disposed) return;
282
+ this.root.updateMatrixWorld(!0);
283
+ const e = new o.Box3().setFromObject(this.model);
284
+ e.isEmpty() || this.layoutGround(e, Math.max(e.getSize(new o.Vector3()).length() * 0.5, 0.02));
285
+ }
286
+ createForceLine() {
287
+ const e = new o.BufferGeometry().setFromPoints([
288
+ new o.Vector3(),
289
+ new o.Vector3()
290
+ ]), t = new o.LineBasicMaterial({
291
+ color: f,
292
+ depthTest: !1,
293
+ transparent: !0
294
+ }), i = new o.Line(e, t);
295
+ return i.renderOrder = 1e3, i.visible = !1, i;
296
+ }
297
+ findBodyGroup(e) {
298
+ var i;
299
+ let t = e;
300
+ for (; t && t !== this.model && t !== this.root; ) {
301
+ if (typeof ((i = t.userData) == null ? void 0 : i.bodyId) == "number") return t;
302
+ t = t.parent;
303
+ }
304
+ return null;
305
+ }
306
+ getBodyGroup(e) {
307
+ let t = this.bodyGroups.get(e);
308
+ return t || this.model.traverse((i) => {
309
+ var r;
310
+ ((r = i.userData) == null ? void 0 : r.bodyId) === e && (t = i, this.bodyGroups.set(e, i));
311
+ }), t;
312
+ }
313
+ pointerRay(e, t, i) {
314
+ const r = i.getBoundingClientRect();
315
+ this.pointer.set(
316
+ (e.clientX - r.left) / r.width * 2 - 1,
317
+ -((e.clientY - r.top) / r.height) * 2 + 1
318
+ ), this.raycaster.setFromCamera(this.pointer, t);
319
+ }
320
+ setForceBoost(e) {
321
+ var t;
322
+ !((t = this.runtime) != null && t.grab) || e === this.forceBoosted || (this.forceBoosted = e, this.runtime.updateGrabStrength(e ? R : 1), this.forceLine.material.color.setHex(e ? G : f));
323
+ }
324
+ beginGrab(e, t, i) {
325
+ if (!this.runtime || this.disposed) return !1;
326
+ this.pointerRay(e, t, i);
327
+ const r = this.raycaster.intersectObject(this.model, !0).find((u) => u.object.isMesh);
328
+ if (!r) return !1;
329
+ const s = this.findBodyGroup(r.object);
330
+ if (!s) return !1;
331
+ const n = s.worldToLocal(r.point.clone());
332
+ this.dragTarget.copy(r.point), L(this.dragPlane, t, r.point), this.dragOffset.set(0, 0, 0);
333
+ const l = this.root.worldToLocal(r.point.clone());
334
+ return this.bodyGroups.set(s.userData.bodyId, s), this.runtime.beginGrab({
335
+ bodyId: s.userData.bodyId,
336
+ localPoint: [n.x, n.y, n.z],
337
+ target: [l.x, l.y, l.z]
338
+ }), this.forceBoosted = !1, this.setForceBoost(e.shiftKey), this.forceLine.visible = !0, this.updateForceLine(), this.grabCanvas = i, i != null && i.style && (this.previousCursor = i.style.cursor, i.style.cursor = "grabbing"), !0;
339
+ }
340
+ moveGrab(e, t, i) {
341
+ var s;
342
+ if (!((s = this.runtime) != null && s.grab)) return;
343
+ if (this.pointerRay(e, t, i), this.setForceBoost(e.shiftKey), i != null && i.style && i.style.cursor !== "grabbing" && (i.style.cursor = "grabbing"), this.raycaster.ray.intersectPlane(this.dragPlane, this.dragIntersection))
344
+ this.dragTarget.copy(this.dragIntersection).add(this.dragOffset);
345
+ else
346
+ return;
347
+ const r = this.root.worldToLocal(this.dragTarget.clone());
348
+ this.runtime.updateGrabTarget([r.x, r.y, r.z]), this.updateForceLine();
349
+ }
350
+ endGrab() {
351
+ var e;
352
+ this.runtime && (this.runtime.endGrab(), this.forceBoosted = !1, this.forceLine.visible = !1, this.forceLine.material.color.setHex(f), (e = this.grabCanvas) != null && e.style && (this.grabCanvas.style.cursor = this.previousCursor), this.grabCanvas = null);
353
+ }
354
+ updateForceLine() {
355
+ var l;
356
+ const e = (l = this.runtime) == null ? void 0 : l.grab;
357
+ if (!e) return;
358
+ const t = this.getBodyGroup(e.bodyId);
359
+ if (!t) return;
360
+ const i = new o.Vector3().fromArray(e.localPoint), r = t.localToWorld(i), s = this.root.worldToLocal(r), n = this.forceLine.geometry.attributes.position;
361
+ n.setXYZ(0, s.x, s.y, s.z), n.setXYZ(1, e.target[0], e.target[1], e.target[2]), n.needsUpdate = !0;
362
+ }
363
+ beforeRender(e, t, i) {
364
+ var n;
365
+ const r = {
366
+ tone: e.toneMapping,
367
+ exposure: e.toneMappingExposure,
368
+ color: e.outputColorSpace,
369
+ environment: t.environment,
370
+ fog: t.fog,
371
+ background: t.background
372
+ }, s = () => {
373
+ e.toneMapping = r.tone, e.toneMappingExposure = r.exposure, e.outputColorSpace = r.color, t.environment = r.environment, t.fog = r.fog, t.background = r.background;
374
+ };
375
+ if (this.disposed) return s;
376
+ try {
377
+ (n = this.runtime) != null && n.grab && this.updateForceLine(), e.toneMapping = o.ACESFilmicToneMapping, e.toneMappingExposure = P[this.presentation], e.outputColorSpace = o.SRGBColorSpace, t.background = new o.Color(this.values.background), t.fog = this.fog;
378
+ const l = e.getSize(new o.Vector2()), u = `${l.x}:${l.y}`;
379
+ return u !== this.size && (this.size = u, this.reflector.setRenderSize(Math.max(1, Math.floor(l.x * 0.35)), Math.max(1, Math.floor(l.y * 0.35)))), t.updateMatrixWorld(!0), i.updateMatrixWorld(!0), this.groundVisible && i instanceof o.PerspectiveCamera && this.reflector.renderReflection(e, t, i), s;
380
+ } catch (l) {
381
+ throw s(), l;
382
+ }
383
+ }
384
+ /**
385
+ * The reflector mesh carries both the grid lines and the reflection. Hiding
386
+ * it also skips the reflection render pass, which only feeds its shader.
387
+ */
388
+ setGroundVisible(e) {
389
+ this.groundVisible = e, this.reflector.visible = e;
390
+ }
391
+ getGroundVisible() {
392
+ return this.groundVisible;
393
+ }
394
+ /**
395
+ * Adds a debug/auxiliary object next to the model rather than inside it.
396
+ * The model subtree is what the reflector pass and `getModelBounds` measure,
397
+ * so nesting hidden aids (collision meshes, which are far larger than the
398
+ * visual shell) would inflate both the reflection extent and the camera fit.
399
+ */
400
+ attachAuxiliary(e) {
401
+ this.auxiliaries.add(e), this.root.add(e);
402
+ }
403
+ getModelBounds() {
404
+ this.root.updateMatrixWorld(!0);
405
+ const e = new o.Box3().setFromObject(this.model);
406
+ if (!e.isEmpty()) return e;
407
+ const t = new o.Box3();
408
+ return t.min.set(-5e3, -5e3, -5e3), t.max.set(5e3, 5e3, 5e3), t;
409
+ }
410
+ dispose() {
411
+ if (!this.disposed) {
412
+ this.disposed = !0, this.endGrab(), this.model.removeFromParent();
413
+ for (const e of this.auxiliaries) e.removeFromParent();
414
+ this.auxiliaries.clear(), this.root.removeFromParent(), this.reflector.renderTarget.dispose(), C(this.root);
415
+ }
416
+ }
417
+ }
418
+ export {
419
+ F as S,
420
+ C as d
421
+ };
package/dist/types.d.ts CHANGED
@@ -1,8 +1,16 @@
1
1
  import type { Box3, ColorRepresentation, Object3D, Vector3 } from 'three';
2
- import type { CadViewerCapability, CameraStateStore, ViewerUiOptions } from './cad/types';
3
- export type { CadBlockModel, CadDocumentHandle, CadDocumentUnits, CadFontResolver, CadFontRequest, CadResolvedFont, CadResolvedTextFont, CadTextWarmupOptions, CadHudModel, CadHudSnapshot, CadInspectorModel, CadInspectorSnapshot, CadLayerModel, CadLayerRow, CadBlockUsageRow, CadSessionPhase, CadWorldBounds, CadScaleRulerState, CadInsertDetails, CadInsertSelection, CadInsertSelectionId, CadTextDetails, CadTextSelection, CadTextSelectionId, CadSelection, CadHit, CadViewerSnapshot, CadLoadProgress, CadViewerErrorCode, CadViewerErrorEvent, CadLoadSource, CadOpenOptions, CadSessionLoadTiming, RenderDebugBreakdownEntry, SplitBlockLayerDetail, CadViewerCapability, ResolveTextFont, CadViewerEventMap, CameraStateStore, ViewerUiKeyboardOptions, ViewerUiOptions, ViewerUiParts, ViewerUiPreset, ViewerUiStateStore, ViewerUiTheme, ViewerUiTokens, } from './cad/types';
2
+ import type { CadViewerCapability, CameraStateStore, ViewerUiOptions, ViewerUiTheme } from './cad/types';
3
+ import type { AssetChangeEvent, AssetControlChangeEvent, AssetErrorEvent, AssetPreview, AssetSource, OpenAssetOptions, ViewerAssetOptions } from './assets/types';
4
+ export type { CadBlockModel, CadDocumentHandle, CadDocumentUnits, CadFontResolver, CadFontRequest, CadResolvedFont, CadResolvedTextFont, CadTextWarmupOptions, CadHudModel, CadHudSnapshot, CadInspectorModel, CadInspectorSnapshot, CadLayerModel, CadLayerRow, CadBlockUsageRow, CadSessionPhase, CadWorldBounds, CadScaleRulerState, CadInsertDetails, CadInsertSelection, CadInsertSelectionId, CadTextDetails, CadTextSelection, CadTextSelectionId, CadSelection, CadHit, CadViewerSnapshot, CadLoadProgress, CadViewerErrorCode, CadViewerErrorEvent, CadLoadSource, CadOpenOptions, CadSessionLoadTiming, RenderDebugBreakdownEntry, SplitBlockLayerDetail, CadViewerCapability, ResolveTextFont, CadViewerEventMap, CameraStateStore, ViewerCadUiKeyboardOptions, ViewerUiOptions, ViewerCadUiParts, ViewerCadUiOptions, ViewerUiPreset, ViewerCadUiStateStore, ViewerUiTheme, ViewerUiTokens, } from './cad/types';
4
5
  export type ViewerViewMode = '2d' | '3d';
5
6
  export type CadInteractionMode = 'managed' | 'manual' | 'disabled';
7
+ /**
8
+ * Which 3D navigation implementation the runtime drives.
9
+ * `viewer` is the CAD path; `preview` is the model/simulation particle viewer
10
+ * matching sim-studio. Kept as its own union so the viewer's public surface
11
+ * does not re-export the runtime package's type.
12
+ */
13
+ export type ViewerNavigationPolicy = 'legacy' | 'viewer' | 'preview';
6
14
  export type CameraChangeReason = 'pointer' | 'wheel' | 'fit' | 'restore' | 'api' | 'view-mode';
7
15
  export type ViewerEnvironment = 'room' | {
8
16
  /** Load an external equirectangular HDR texture for image-based lighting. */
@@ -72,6 +80,8 @@ export interface ViewerOptions {
72
80
  /** Max delay for merged viewport demands while the interaction is active. Default 160ms. */
73
81
  viewportDemandCoalesceMs?: number;
74
82
  ui?: ViewerUiOptions;
83
+ assets?: ViewerAssetOptions;
84
+ onCleanupError?: (event: AssetErrorEvent) => void;
75
85
  }
76
86
  export interface ViewerFitViewOptions {
77
87
  padding?: number;
@@ -126,6 +136,9 @@ export interface ViewerEventMap {
126
136
  'view-mode-change': {
127
137
  viewMode: ViewerViewMode;
128
138
  };
139
+ 'theme-change': {
140
+ theme: ViewerUiTheme;
141
+ };
129
142
  resize: {
130
143
  resized: boolean;
131
144
  };
@@ -156,6 +169,9 @@ export interface ViewerEventMap {
156
169
  layerPanelOpen: boolean;
157
170
  propertyPanelOpen: boolean;
158
171
  };
172
+ 'asset-change': AssetChangeEvent;
173
+ 'asset-error': AssetErrorEvent;
174
+ 'asset-control-change': AssetControlChangeEvent;
159
175
  }
160
176
  export interface Viewer {
161
177
  readonly viewport: HTMLElement;
@@ -184,12 +200,16 @@ export interface Viewer {
184
200
  execute(id: string, args?: unknown): boolean;
185
201
  has(id: string): boolean;
186
202
  };
203
+ openAsset(source: AssetSource, options?: OpenAssetOptions): AssetPreview;
204
+ closeAsset(): Promise<void>;
187
205
  addObject(object: Object3D, options?: ViewerObjectOptions): void;
188
206
  removeObject(object: Object3D): boolean;
189
207
  setVisible(object: Object3D, visible: boolean): void;
190
208
  setViewMode(mode: ViewerViewMode, options?: {
191
209
  preserveOrbit?: boolean;
192
210
  }): void;
211
+ /** Switches the UI and active preview stage theme at runtime. */
212
+ setTheme(theme: ViewerUiTheme): boolean;
193
213
  fitView(target?: Object3D | Box3, options?: ViewerFitViewOptions): boolean;
194
214
  fitAll(options?: ViewerFitViewOptions): boolean;
195
215
  screenToWorldOnPlane(clientX: number, clientY: number, planeZ?: number): Vector3 | null;
@@ -199,6 +219,8 @@ export interface Viewer {
199
219
  on<K extends keyof ViewerEventMap>(type: K, listener: (event: ViewerEventMap[K]) => void): () => void;
200
220
  resize(): boolean;
201
221
  requestRender(): void;
222
+ getActiveCamera(): import('three').Camera;
223
+ setNavigationEnabled(enabled: boolean): void;
202
224
  dispose(): void;
203
225
  use(plugins: readonly import('./plugins').ViewerPlugin[], options?: {
204
226
  replace?: boolean;
@@ -0,0 +1,45 @@
1
+ import type { ViewerCadUiOptions } from '../cad/types';
2
+ /** Theme shared by every package-owned UI group. */
3
+ export type ViewerUiTheme = 'dark' | 'light';
4
+ /** Which built-in UI groups the viewer registers. */
5
+ export type ViewerUiPreset = 'auto' | 'cad-readonly' | 'none';
6
+ /** Options for the built-in model/simulation scene UI group. */
7
+ export interface ViewerSceneUiOptions {
8
+ parts?: ViewerSceneUiParts;
9
+ }
10
+ export interface ViewerSceneUiParts {
11
+ filename?: boolean;
12
+ playback?: boolean;
13
+ reset?: boolean;
14
+ fit?: boolean;
15
+ upAxis?: boolean;
16
+ ground?: boolean;
17
+ collision?: boolean;
18
+ theme?: boolean;
19
+ }
20
+ /** Shared UI configuration; asset-specific controls live in their named groups. */
21
+ export interface ViewerUiOptions {
22
+ root: HTMLElement;
23
+ preset?: ViewerUiPreset;
24
+ /** Single initial theme source; built-in buttons switch it internally at runtime. */
25
+ theme?: ViewerUiTheme;
26
+ tokens?: ViewerUiTokens;
27
+ style?: string | ((root: ShadowRoot) => void);
28
+ locale?: string;
29
+ messages?: Readonly<Record<string, string>>;
30
+ messageResolver?: (key: string, params?: Readonly<Record<string, string | number>>) => string | undefined;
31
+ /** Defaults to the viewport; 'none' only broadcasts layout changes. */
32
+ insetTarget?: HTMLElement | 'none';
33
+ cad?: ViewerCadUiOptions;
34
+ /** Shared scene controls for model and simulation assets, gated by capabilities. */
35
+ scene?: ViewerSceneUiOptions;
36
+ }
37
+ export interface ViewerUiTokens {
38
+ accent?: string;
39
+ panelBackground?: string;
40
+ background?: string;
41
+ text?: string;
42
+ textDim?: string;
43
+ border?: string;
44
+ hudBackground?: string;
45
+ }