@nebula-spatial/viewer 0.2.4 → 0.4.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.
@@ -0,0 +1,93 @@
1
+ import { Quaternion as y, Vector3 as M, Group as P, Box3 as T } from "three";
2
+ import { DRACOLoader as x } from "three/examples/jsm/loaders/DRACOLoader.js";
3
+ import { GLTFLoader as D } from "three/examples/jsm/loaders/GLTFLoader.js";
4
+ import { S as E, d as w } from "./stage-BxROW_dP.js";
5
+ const G = new y().setFromAxisAngle(new M(1, 0, 0), Math.PI / 2), R = new y();
6
+ function S(r) {
7
+ return r === "y" ? G : R;
8
+ }
9
+ function b(r, n) {
10
+ const t = S(n);
11
+ return t.equals(r.quaternion) ? !1 : (r.quaternion.copy(t), r.updateMatrixWorld(!0), !0);
12
+ }
13
+ function z(r, n) {
14
+ let t = !1, i = null, e = null, d = "z", f;
15
+ const l = new x(), p = new D(), m = () => {
16
+ t = !0;
17
+ }, a = (async () => {
18
+ d = n.options.upAxis ?? "z", n.options.dracoDecoderPath && l.setDecoderPath(n.options.dracoDecoderPath), p.setDRACOLoader(l);
19
+ let o = null;
20
+ try {
21
+ if (t || n.signal.aborted) throw h();
22
+ const s = await p.loadAsync(r.url ?? await r.resolve(r.entryPath ?? ""));
23
+ o = new P(), o.name = "viewer-model-root";
24
+ const u = s.scenes.filter((c) => c !== s.scene);
25
+ if (o.add(s.scene), t || n.signal.aborted) {
26
+ for (const c of u) o.add(c);
27
+ throw h();
28
+ }
29
+ b(o, d), g(o);
30
+ const A = new E(o, n.options.simulationTheme, void 0, "model");
31
+ i = o, e = A, o = null;
32
+ } finally {
33
+ o && w(o), l.dispose();
34
+ }
35
+ })();
36
+ return a.catch(() => {
37
+ }), {
38
+ id: `model-${Math.random().toString(36).slice(2)}`,
39
+ opened: a,
40
+ get root() {
41
+ return (e == null ? void 0 : e.root) ?? null;
42
+ },
43
+ getFitBounds() {
44
+ return (e == null ? void 0 : e.getModelBounds()) ?? null;
45
+ },
46
+ beforeRender(o, s, u) {
47
+ return e == null ? void 0 : e.beforeRender(o, s, u);
48
+ },
49
+ capabilities: { canPlay: !1, canPause: !1, canReset: !1, hasPhysics: !1, canReorientUpAxis: !0, canToggleGround: !0 },
50
+ // Session presentation overrides are only consulted for `interactionMode`
51
+ // and `fitPolicy`; the UI group comes from the loader descriptor. Keep the
52
+ // two in sync so the override never reads as a contradictory value.
53
+ presentation: { preferredViewMode: "3d", interactionMode: "object", uiProfile: "model", fitPolicy: "fit-on-ready" },
54
+ getUpAxis() {
55
+ return i ? d : null;
56
+ },
57
+ setTheme(o) {
58
+ e == null || e.setTheme(o);
59
+ },
60
+ setGroundVisible(o) {
61
+ e == null || e.setGroundVisible(o);
62
+ },
63
+ getGroundVisible() {
64
+ return (e == null ? void 0 : e.getGroundVisible()) ?? !0;
65
+ },
66
+ setUpAxis(o) {
67
+ if (t || !i || !e || !b(i, o)) return;
68
+ d = o, e.reseatGround();
69
+ const s = e.getModelBounds();
70
+ n.requestRender(), n.fit(s, { padding: 1.15 });
71
+ },
72
+ beginDispose: m,
73
+ dispose() {
74
+ return m(), f ?? (f = a.catch(() => {
75
+ }).then(() => {
76
+ e == null || e.dispose(), e = null, i && w(i), i = null;
77
+ })), f;
78
+ }
79
+ };
80
+ }
81
+ function g(r) {
82
+ r.updateMatrixWorld(!0);
83
+ const n = new T().setFromObject(r);
84
+ if (n.isEmpty()) throw new Error("模型不包含可显示的几何内容。");
85
+ const t = n.getSize(new M()), i = Math.max(t.x, t.y, t.z);
86
+ if (!Number.isFinite(i) || i <= 0) throw new Error("模型尺寸无效。");
87
+ }
88
+ function h() {
89
+ return Object.assign(new Error("Model load aborted."), { name: "AbortError", code: "E_ABORTED" });
90
+ }
91
+ export {
92
+ z as createModelSession
93
+ };
@@ -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-Lsk3kHNe.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,7 +1,17 @@
1
1
  import type { Box3, ColorRepresentation, Object3D, Vector3 } from 'three';
2
- import type { CadViewerCapability, CameraStateStore, ViewerUiOptions } from './cad/types';
3
- export type { CadBlockModel, CadDocumentHandle, CadFontResolver, CadResolvedTextFont, CadHudModel, CadInspectorModel, CadLayerModel, CadLoadSource, CadOpenOptions, CadSessionLoadTiming, CadTextDetails, CadTextSelection, CadTextSelectionId, CadViewerCapability, ResolveTextFont, CadViewerEventMap, CameraStateStore, ViewerUiOptions, ViewerUiParts, ViewerUiPreset, ViewerUiTheme, } 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';
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';
14
+ export type CameraChangeReason = 'pointer' | 'wheel' | 'fit' | 'restore' | 'api' | 'view-mode';
5
15
  export type ViewerEnvironment = 'room' | {
6
16
  /** Load an external equirectangular HDR texture for image-based lighting. */
7
17
  readonly type: 'hdr';
@@ -50,10 +60,28 @@ export interface ViewerOptions {
50
60
  environment?: ViewerEnvironment;
51
61
  autoSelectOnPick?: boolean;
52
62
  pointerPick?: boolean;
63
+ /** CAD click policy. Defaults to managed. */
64
+ cadInteraction?: CadInteractionMode;
65
+ cadInteractionStyle?: {
66
+ hover?: {
67
+ color?: ColorRepresentation;
68
+ fillOpacity?: number;
69
+ edgeOpacity?: number;
70
+ enabled?: boolean;
71
+ };
72
+ selected?: {
73
+ color?: ColorRepresentation;
74
+ fillOpacity?: number;
75
+ edgeOpacity?: number;
76
+ enabled?: boolean;
77
+ };
78
+ };
53
79
  cameraStateStore?: CameraStateStore | false;
54
80
  /** Max delay for merged viewport demands while the interaction is active. Default 160ms. */
55
81
  viewportDemandCoalesceMs?: number;
56
82
  ui?: ViewerUiOptions;
83
+ assets?: ViewerAssetOptions;
84
+ onCleanupError?: (event: AssetErrorEvent) => void;
57
85
  }
58
86
  export interface ViewerFitViewOptions {
59
87
  padding?: number;
@@ -75,6 +103,10 @@ export interface ViewerPickResult {
75
103
  export interface ViewerHighlightOptions {
76
104
  color?: ColorRepresentation;
77
105
  }
106
+ export interface ViewerObjectOptions {
107
+ /** Viewer only mounts the object; the caller retains responsibility for GPU resource disposal. */
108
+ readonly ownership?: 'borrowed';
109
+ }
78
110
  export interface ViewerRenderStats {
79
111
  readonly drawCalls: number;
80
112
  readonly submittedVertexRefs: number;
@@ -104,6 +136,9 @@ export interface ViewerEventMap {
104
136
  'view-mode-change': {
105
137
  viewMode: ViewerViewMode;
106
138
  };
139
+ 'theme-change': {
140
+ theme: ViewerUiTheme;
141
+ };
107
142
  resize: {
108
143
  resized: boolean;
109
144
  };
@@ -121,7 +156,22 @@ export interface ViewerEventMap {
121
156
  };
122
157
  'camera-change': {
123
158
  viewMode: ViewerViewMode;
159
+ reason: CameraChangeReason;
160
+ interaction: 'active' | 'idle';
161
+ };
162
+ 'ui-layout-change': {
163
+ contentInsets: {
164
+ readonly top: number;
165
+ readonly right: number;
166
+ readonly bottom: number;
167
+ readonly left: number;
168
+ };
169
+ layerPanelOpen: boolean;
170
+ propertyPanelOpen: boolean;
124
171
  };
172
+ 'asset-change': AssetChangeEvent;
173
+ 'asset-error': AssetErrorEvent;
174
+ 'asset-control-change': AssetControlChangeEvent;
125
175
  }
126
176
  export interface Viewer {
127
177
  readonly viewport: HTMLElement;
@@ -131,13 +181,37 @@ export interface Viewer {
131
181
  readonly highlightedObject: Object3D | null;
132
182
  readonly isDisposed: boolean;
133
183
  readonly cad: CadViewerCapability;
134
- addObject(object: Object3D): void;
184
+ readonly overlays: {
185
+ set(key: string, object: Object3D, options?: {
186
+ owned?: boolean;
187
+ }): void;
188
+ remove(key: string, options?: {
189
+ dispose?: boolean;
190
+ }): Object3D | null;
191
+ };
192
+ readonly camera: {
193
+ getState(): ViewerCameraSnapshot | null;
194
+ /** Applies a validated snapshot immediately. Animated transitions are not part of the current contract. */
195
+ setState(snapshot: ViewerCameraSnapshot): boolean;
196
+ reset(): boolean;
197
+ };
198
+ readonly plugins: import('./plugins').ViewerPluginRegistry;
199
+ readonly commands: {
200
+ execute(id: string, args?: unknown): boolean;
201
+ has(id: string): boolean;
202
+ };
203
+ openAsset(source: AssetSource, options?: OpenAssetOptions): AssetPreview;
204
+ closeAsset(): Promise<void>;
205
+ addObject(object: Object3D, options?: ViewerObjectOptions): void;
135
206
  removeObject(object: Object3D): boolean;
136
207
  setVisible(object: Object3D, visible: boolean): void;
137
208
  setViewMode(mode: ViewerViewMode, options?: {
138
209
  preserveOrbit?: boolean;
139
210
  }): void;
211
+ /** Switches the UI and active preview stage theme at runtime. */
212
+ setTheme(theme: ViewerUiTheme): boolean;
140
213
  fitView(target?: Object3D | Box3, options?: ViewerFitViewOptions): boolean;
214
+ fitAll(options?: ViewerFitViewOptions): boolean;
141
215
  screenToWorldOnPlane(clientX: number, clientY: number, planeZ?: number): Vector3 | null;
142
216
  pick(clientX: number, clientY: number, options?: ViewerPickOptions): ViewerPickResult | null;
143
217
  setSelection(object: Object3D | null): void;
@@ -145,5 +219,10 @@ export interface Viewer {
145
219
  on<K extends keyof ViewerEventMap>(type: K, listener: (event: ViewerEventMap[K]) => void): () => void;
146
220
  resize(): boolean;
147
221
  requestRender(): void;
222
+ getActiveCamera(): import('three').Camera;
223
+ setNavigationEnabled(enabled: boolean): void;
148
224
  dispose(): void;
225
+ use(plugins: readonly import('./plugins').ViewerPlugin[], options?: {
226
+ replace?: boolean;
227
+ }): void;
149
228
  }