@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,208 @@
1
+ var E = Object.defineProperty;
2
+ var M = (r, t, i) => t in r ? E(r, t, { enumerable: !0, configurable: !0, writable: !0, value: i }) : r[t] = i;
3
+ var p = (r, t, i) => M(r, typeof t != "symbol" ? t + "" : t, i);
4
+ import T from "@mujoco/mujoco";
5
+ import I from "@mujoco/mujoco/mujoco.wasm?url";
6
+ let w = 0;
7
+ function F(r, t) {
8
+ const i = `/viewer-simulation-${++w}`;
9
+ r.mkdir(i);
10
+ const n = /* @__PURE__ */ new Set([i]), o = /* @__PURE__ */ new Set(), a = (e, s) => {
11
+ const c = e.replaceAll("\\", "/").split("/");
12
+ if (c[0] === "" || c.some((u) => u === ".." || u.includes(":"))) throw b(`Invalid package path: ${e}`, "E_SIMULATION_PATH");
13
+ const l = c.filter((u) => u && u !== ".");
14
+ if (!l.length) throw b("Package path is empty.", "E_SIMULATION_PATH");
15
+ let m = i;
16
+ for (const u of l.slice(0, -1))
17
+ m += `/${u}`, n.has(m) || (r.mkdir(m), n.add(m));
18
+ const d = `${i}/${l.join("/")}`;
19
+ return r.writeFile(d, s), o.add(d), d;
20
+ }, h = () => {
21
+ for (const e of o) r.unlink(e);
22
+ o.clear();
23
+ for (const e of [...n].reverse()) r.rmdir(e);
24
+ n.clear();
25
+ };
26
+ try {
27
+ for (const [e, s] of t) a(e, s);
28
+ return { writeEntry: (e) => a("__viewer_entry.xml", e), dispose: h };
29
+ } catch (e) {
30
+ throw h(), e;
31
+ }
32
+ }
33
+ function b(r, t) {
34
+ return Object.assign(new Error(r), { code: t });
35
+ }
36
+ function O({
37
+ target: r,
38
+ point: t,
39
+ velocity: i,
40
+ stiffness: n,
41
+ damping: o,
42
+ maxForce: a,
43
+ strength: h
44
+ }) {
45
+ const e = [
46
+ n * (r[0] - t[0]) - o * i[0],
47
+ n * (r[1] - t[1]) - o * i[1],
48
+ n * (r[2] - t[2]) - o * i[2]
49
+ ], s = Math.hypot(e[0], e[1], e[2]), c = a * h;
50
+ if (s > c && s > 0) {
51
+ const l = c / s;
52
+ e[0] *= l, e[1] *= l, e[2] *= l;
53
+ }
54
+ return e;
55
+ }
56
+ const f = 1 / 240, A = 0.05, v = 80, x = 1.15, g = 3.5, y = 14;
57
+ class C {
58
+ constructor() {
59
+ p(this, "mujoco", null);
60
+ p(this, "model", null);
61
+ p(this, "data", null);
62
+ p(this, "grab", null);
63
+ p(this, "lastGrabForce", [0, 0, 0]);
64
+ p(this, "velocityBuffer", new Float64Array(6));
65
+ p(this, "initialControls", []);
66
+ p(this, "accumulator", 0);
67
+ p(this, "initialVelocities", []);
68
+ }
69
+ async initialize(t, i) {
70
+ var h;
71
+ this.mujoco ?? (this.mujoco = await T({ locateFile: (e) => e.endsWith(".wasm") ? i ? new URL(e, i).href : I : e }));
72
+ const n = F(this.mujoco.FS, t.files);
73
+ let o = null, a = null;
74
+ try {
75
+ o = this.mujoco.MjModel.from_xml_path(n.writeEntry(t.xml));
76
+ for (const e of t.massOverrides ?? []) {
77
+ const s = this.mujoco.mj_name2id(o, this.mujoco.mjtObj.mjOBJ_BODY.value, e.body);
78
+ if (s <= 0 || !Number.isFinite(e.mass) || e.mass <= 0 || !(o.body_mass[s] > 0)) throw new Error(`Invalid mass override: ${e.body}`);
79
+ const c = e.mass / o.body_mass[s];
80
+ o.body_mass[s] = e.mass;
81
+ for (let l = 0; l < 3; l++) o.body_inertia[s * 3 + l] *= c;
82
+ }
83
+ a = new this.mujoco.MjData(o), (h = t.massOverrides) != null && h.length && this.mujoco.mj_setConst(o, a), o.opt.timestep = f, this.applyInitialControls(a, t.initialControls), this.applyInitialVelocities(o, a, t.initialVelocities), this.mujoco.mj_forward(o, a);
84
+ } catch (e) {
85
+ throw a == null || a.delete(), o == null || o.delete(), _(e, "E_MUJOCO_INITIALIZE");
86
+ } finally {
87
+ n.dispose();
88
+ }
89
+ this.disposeModel(), this.model = o, this.data = a, this.initialControls = t.initialControls ?? [], this.initialVelocities = t.initialVelocities ?? [], this.accumulator = 0;
90
+ }
91
+ beginGrab({
92
+ bodyId: t,
93
+ localPoint: i,
94
+ target: n,
95
+ stiffness: o = v,
96
+ damping: a,
97
+ maxForce: h,
98
+ dampingRatio: e = x,
99
+ maxTargetSpeed: s = g,
100
+ targetResponse: c = y
101
+ }) {
102
+ var j;
103
+ const l = Math.max(((j = this.model) == null ? void 0 : j.body_mass[t]) ?? 1, 1e-3), m = a ?? 2 * Math.sqrt(o * l) * e, d = h ?? Math.max(120, l * 60), u = [n[0], n[1], n[2]];
104
+ this.grab = {
105
+ bodyId: t,
106
+ localPoint: [i[0], i[1], i[2]],
107
+ target: [...u],
108
+ requestedTarget: u,
109
+ stiffness: o,
110
+ damping: m,
111
+ maxForce: d,
112
+ strength: 1,
113
+ maxTargetSpeed: s,
114
+ targetResponse: c
115
+ };
116
+ }
117
+ updateGrabTarget(t) {
118
+ this.grab && (this.grab.requestedTarget = [t[0], t[1], t[2]]);
119
+ }
120
+ updateGrabStrength(t) {
121
+ this.grab && (this.grab.strength = t);
122
+ }
123
+ applyGrabForce() {
124
+ if (!this.grab || !this.model || !this.data || !this.mujoco) return;
125
+ const { bodyId: t, localPoint: i, target: n, stiffness: o, damping: a, maxForce: h, strength: e } = this.grab, s = this.data.xmat.subarray(t * 9, t * 9 + 9), c = this.data.xpos.subarray(t * 3, t * 3 + 3), l = [
126
+ c[0] + s[0] * i[0] + s[1] * i[1] + s[2] * i[2],
127
+ c[1] + s[3] * i[0] + s[4] * i[1] + s[5] * i[2],
128
+ c[2] + s[6] * i[0] + s[7] * i[1] + s[8] * i[2]
129
+ ];
130
+ this.mujoco.mj_objectVelocity(
131
+ this.model,
132
+ this.data,
133
+ this.mujoco.mjtObj.mjOBJ_BODY.value,
134
+ t,
135
+ this.velocityBuffer,
136
+ 0
137
+ );
138
+ const m = this.velocityBuffer, d = [
139
+ l[0] - c[0],
140
+ l[1] - c[1],
141
+ l[2] - c[2]
142
+ ], u = [
143
+ m[3] + m[1] * d[2] - m[2] * d[1],
144
+ m[4] + m[2] * d[0] - m[0] * d[2],
145
+ m[5] + m[0] * d[1] - m[1] * d[0]
146
+ ], j = O({ target: n, point: l, velocity: u, stiffness: o, damping: a, maxForce: h, strength: e });
147
+ this.lastGrabForce = [...j], this.data.qfrc_applied.fill(0), this.mujoco.mj_applyFT(
148
+ this.model,
149
+ this.data,
150
+ j,
151
+ [0, 0, 0],
152
+ l,
153
+ t,
154
+ this.data.qfrc_applied
155
+ );
156
+ }
157
+ endGrab() {
158
+ this.data && this.data.qfrc_applied.fill(0), this.lastGrabForce = [0, 0, 0], this.grab = null;
159
+ }
160
+ step(t) {
161
+ if (!this.mujoco || !this.model || !this.data || t <= 0) return !1;
162
+ this.accumulator += Math.min(t, A);
163
+ let i = !1;
164
+ for (; this.accumulator >= f; )
165
+ this.updateGrabTargetForStep(), this.applyGrabForce(), this.mujoco.mj_step(this.model, this.data), this.accumulator -= f, i = !0;
166
+ return i;
167
+ }
168
+ updateGrabTargetForStep() {
169
+ const t = this.grab;
170
+ if (!t) return;
171
+ const i = t.requestedTarget ?? t.target, n = (t.maxTargetSpeed ?? g) * Math.max(1, Math.sqrt(t.strength)), o = t.targetResponse ?? y, a = 1 - Math.exp(-o * f), h = n * f;
172
+ for (let e = 0; e < 3; e++) {
173
+ const s = i[e] - t.target[e], c = Math.sign(s) * Math.min(Math.abs(s) * a, h);
174
+ t.target[e] += c;
175
+ }
176
+ }
177
+ reset() {
178
+ !this.mujoco || !this.model || !this.data || (this.endGrab(), this.mujoco.mj_resetData(this.model, this.data), this.applyInitialControls(this.data, this.initialControls), this.applyInitialVelocities(this.model, this.data, this.initialVelocities), this.mujoco.mj_forward(this.model, this.data), this.accumulator = 0);
179
+ }
180
+ dispose() {
181
+ this.disposeModel(), this.mujoco = null;
182
+ }
183
+ applyInitialControls(t, i) {
184
+ if (i != null && i.length) {
185
+ if (i.length !== t.ctrl.length) throw _(new Error(`Initial control count ${i.length} does not match MuJoCo actuator count ${t.ctrl.length}.`), "E_MUJOCO_INITIALIZE");
186
+ t.ctrl.set(i);
187
+ }
188
+ }
189
+ applyInitialVelocities(t, i, n) {
190
+ for (const o of n ?? []) {
191
+ const a = this.mujoco.mj_name2id(t, this.mujoco.mjtObj.mjOBJ_BODY.value, o.body), h = a >= 0 ? t.body_jntadr[a] : -1;
192
+ if (h < 0 || t.jnt_type[h] !== this.mujoco.mjtJoint.mjJNT_FREE.value || o.velocity.length !== 6 || !o.velocity.every(Number.isFinite))
193
+ throw _(new Error(`Invalid free-body initial velocity: ${o.body}`), "E_MUJOCO_INITIALIZE");
194
+ i.qvel.set(o.velocity, t.jnt_dofadr[h]);
195
+ }
196
+ }
197
+ disposeModel() {
198
+ var t, i;
199
+ this.endGrab(), (t = this.data) == null || t.delete(), (i = this.model) == null || i.delete(), this.data = null, this.model = null, this.initialControls = [], this.initialVelocities = [], this.accumulator = 0;
200
+ }
201
+ }
202
+ function _(r, t) {
203
+ const i = r instanceof Error ? r : new Error(String(r));
204
+ return Object.assign(i, { code: t });
205
+ }
206
+ export {
207
+ C as MujocoRuntime
208
+ };
package/dist/plugins.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import type { Box3, Object3D } from 'three';
2
2
  import type { CadHit, CadSelection, CadViewerSnapshot, CadViewerEventMap } from './cad/types';
3
- import type { ViewerEventMap, ViewerViewMode } from './types';
3
+ import type { ViewerEventMap, ViewerViewMode, ViewerUiTheme } from './types';
4
+ import type { AssetControlState, AssetLoaderContribution, AssetPreviewSnapshot, AssetUpAxis } from './assets/types';
4
5
  export interface Disposable {
5
6
  dispose(): void;
6
7
  }
@@ -9,6 +10,9 @@ export interface ToolbarItem {
9
10
  readonly label: string;
10
11
  readonly title?: string;
11
12
  readonly disabled?: boolean;
13
+ readonly pressed?: boolean;
14
+ /** Inline SVG markup (24x24 viewBox, `currentColor` stroke). */
15
+ readonly icon?: string;
12
16
  readonly onClick: () => void;
13
17
  }
14
18
  export interface ViewerState {
@@ -17,10 +21,18 @@ export interface ViewerState {
17
21
  readonly highlightedObject: Object3D | null;
18
22
  readonly isDisposed: boolean;
19
23
  }
24
+ /** Read-only view of the active asset. Never exposes the mutable `AssetPreview` handle. */
25
+ export interface ViewerAssetReadApi {
26
+ /** Snapshot of the active asset; `null` when no asset is open. */
27
+ $read(): AssetPreviewSnapshot | null;
28
+ /** Authoritative control state, safe to bind button pressed states to. */
29
+ $readControl(): AssetControlState;
30
+ }
20
31
  export interface ViewerPluginContext {
21
32
  readonly cad: {
22
33
  $read(): CadViewerSnapshot;
23
34
  };
35
+ readonly asset: ViewerAssetReadApi;
24
36
  readonly viewer: {
25
37
  $getState(): ViewerState;
26
38
  };
@@ -29,6 +41,14 @@ export interface ViewerPluginContext {
29
41
  setSelection(selection: CadSelection | null): void;
30
42
  fitDocument(): boolean;
31
43
  setViewMode(mode: ViewerViewMode): void;
44
+ play(): boolean;
45
+ pause(): boolean;
46
+ reset(): boolean;
47
+ fitView(): boolean;
48
+ setUpAxis(axis: AssetUpAxis): boolean;
49
+ setGroundVisible(visible: boolean): boolean;
50
+ setCollisionVisible(visible: boolean): boolean;
51
+ setTheme(theme: ViewerUiTheme): boolean;
32
52
  };
33
53
  readonly events: {
34
54
  on<K extends keyof (ViewerEventMap & CadViewerEventMap)>(type: K, listener: (event: (ViewerEventMap & CadViewerEventMap)[K]) => void): () => void;
@@ -82,7 +102,7 @@ export type ViewerContributionPoint = {
82
102
  readonly kind: 'fit-policy';
83
103
  readonly id: string;
84
104
  readonly resolve: (ctx: ViewerPluginContext) => Box3 | null;
85
- };
105
+ } | AssetLoaderContribution;
86
106
  export interface ViewerPlugin {
87
107
  readonly id: string;
88
108
  activate(context: ViewerPluginContext): void | Promise<void>;
@@ -90,25 +110,49 @@ export interface ViewerPlugin {
90
110
  }
91
111
  type SnapshotReader = () => CadViewerSnapshot;
92
112
  type StateReader = () => ViewerState;
113
+ type AssetReader = () => AssetPreviewSnapshot | null;
114
+ type ControlStateReader = () => AssetControlState;
93
115
  type PluginEventMap = ViewerEventMap & CadViewerEventMap;
94
116
  type EventSubscriber = <K extends keyof PluginEventMap>(type: K, listener: (event: PluginEventMap[K]) => void) => () => void;
95
117
  type CommandApi = ViewerPluginContext['commands'];
118
+ /**
119
+ * Layout channel the plugin manager delegates to. The engine owns zone
120
+ * containers, insets computation and the `ui-layout-change` broadcast; the
121
+ * manager only forwards contribution nodes into it.
122
+ */
123
+ export interface PluginLayoutPort {
124
+ mountPlugin(node: Node, position?: 'left' | 'right' | 'top' | 'bottom' | 'overlay'): Disposable;
125
+ /** Built-in UI path: the package owns the styling of nodes mounted this way. */
126
+ mountBuiltin(node: Node, options: {
127
+ zone: 'left' | 'right' | 'top' | 'bottom' | 'overlay';
128
+ group?: string;
129
+ }): Disposable;
130
+ }
96
131
  export declare class ViewerPluginManager implements ViewerPluginRegistry {
97
132
  private readonly readCad;
98
133
  private readonly readState;
134
+ private readonly readAsset;
135
+ private readonly readControlState;
99
136
  private readonly subscribe;
100
137
  private readonly commands;
101
138
  private readonly hostElement;
139
+ private readonly layout;
102
140
  private readonly pluginsById;
103
141
  private readonly contributions;
104
142
  private readonly commandContributions;
143
+ private readonly builtinPlugins;
144
+ private readonly contributionRemovedListeners;
105
145
  private refreshingInspectors;
106
146
  private disposed;
107
- constructor(readCad: SnapshotReader, readState: StateReader, subscribe: EventSubscriber, commands: CommandApi, hostElement: HTMLElement);
147
+ constructor(readCad: SnapshotReader, readState: StateReader, readAsset: AssetReader, readControlState: ControlStateReader, subscribe: EventSubscriber, commands: CommandApi, hostElement: HTMLElement, layout?: PluginLayoutPort | null);
108
148
  register(plugin: ViewerPlugin): Disposable;
109
149
  use(plugins: readonly ViewerPlugin[], options?: {
110
150
  replace?: boolean;
111
151
  }): void;
152
+ registerBuiltin(plugin: ViewerPlugin): Disposable;
153
+ getAssetLoaders(): readonly AssetLoaderContribution[];
154
+ onAssetLoaderRemoved(listener: (loader: AssetLoaderContribution) => void): Disposable;
155
+ private onContributionRemoved;
112
156
  private deactivate;
113
157
  disposeAll(): void;
114
158
  dispose(): void;
@@ -0,0 +1,266 @@
1
+ import { Mesh as F, MeshPhongMaterial as m, MeshLambertMaterial as D, MeshStandardMaterial as L, Color as A, Quaternion as w, Vector3 as B, Group as E, LoaderUtils as C, Box3 as N } from "three";
2
+ import { DRACOLoader as v } from "three/examples/jsm/loaders/DRACOLoader.js";
3
+ import { FBXLoader as G } from "three/examples/jsm/loaders/FBXLoader.js";
4
+ import { GLTFLoader as _ } from "three/examples/jsm/loaders/GLTFLoader.js";
5
+ import { S as R, d as O } from "./stage-CFr9ZkwQ.js";
6
+ const b = "Kaydara FBX Binary \0", X = /(?:^|[\r\n])\s*P:\s*"UpAxis"\s*,\s*"int"\s*,\s*"Integer"\s*,\s*""\s*,\s*(-?\d+)/i, z = /(?:^|[\r\n])\s*P:\s*"UpAxisSign"\s*,\s*"int"\s*,\s*"Integer"\s*,\s*""\s*,\s*(-?\d+)/i;
7
+ function V(e) {
8
+ try {
9
+ const n = new Uint8Array(e), r = q(n) ? Y(e) : W(new TextDecoder().decode(n));
10
+ return !r || r.sign !== 1 ? null : r.axis === 1 ? "y" : r.axis === 2 ? "z" : null;
11
+ } catch {
12
+ return null;
13
+ }
14
+ }
15
+ function q(e) {
16
+ if (e.byteLength < b.length) return !1;
17
+ for (let n = 0; n < b.length; n += 1)
18
+ if (e[n] !== b.charCodeAt(n)) return !1;
19
+ return !0;
20
+ }
21
+ function W(e) {
22
+ var t, i;
23
+ const n = Number.parseInt(((t = X.exec(e)) == null ? void 0 : t[1]) ?? "", 10);
24
+ if (!Number.isInteger(n)) return null;
25
+ const r = (i = z.exec(e)) == null ? void 0 : i[1], s = r === void 0 ? 1 : Number.parseInt(r, 10);
26
+ return Number.isInteger(s) ? { axis: n, sign: s } : null;
27
+ }
28
+ function Y(e) {
29
+ const n = new DataView(e);
30
+ if (n.byteLength < 27) return null;
31
+ const r = n.getUint32(23, !0) >= 7500;
32
+ let s = 27;
33
+ for (; s < n.byteLength; ) {
34
+ const t = T(n, s, r, !1);
35
+ if (!t) break;
36
+ if (t.name === "GlobalSettings") return P(n, t.childrenOffset, t.endOffset, r);
37
+ s = t.endOffset;
38
+ }
39
+ return null;
40
+ }
41
+ function P(e, n, r, s) {
42
+ let t = n, i = null, l = 1;
43
+ for (; t < r; ) {
44
+ const a = T(e, t, s, !0);
45
+ if (!a) break;
46
+ if (a.name === "P") {
47
+ const [u, , , , o] = a.properties;
48
+ u === "UpAxis" && typeof o == "number" && (i = o), u === "UpAxisSign" && typeof o == "number" && (l = o);
49
+ }
50
+ if (a.childrenOffset < a.endOffset) {
51
+ const u = P(e, a.childrenOffset, a.endOffset, s);
52
+ u && (i ?? (i = u.axis), l = u.sign);
53
+ }
54
+ t = a.endOffset;
55
+ }
56
+ return i === null ? null : { axis: i, sign: l };
57
+ }
58
+ function T(e, n, r, s) {
59
+ const t = r ? 25 : 13;
60
+ if (n + t > e.byteLength) return null;
61
+ const i = M(e, n, r), l = M(e, n + (r ? 8 : 4), r), a = M(e, n + (r ? 16 : 8), r), u = e.getUint8(n + (r ? 24 : 12));
62
+ if (i === 0) return null;
63
+ const o = n + t, c = o + u, f = c + a;
64
+ if (i > e.byteLength || f > i) return null;
65
+ const d = new TextDecoder().decode(new Uint8Array(e.buffer, e.byteOffset + o, u)), p = [];
66
+ if (s) {
67
+ let h = c;
68
+ for (let y = 0; y < l; y += 1) {
69
+ const g = $(e, h);
70
+ if (p.push(g.value), h = g.nextOffset, h > f) return null;
71
+ }
72
+ }
73
+ return { name: d, endOffset: i, childrenOffset: f, properties: p };
74
+ }
75
+ function M(e, n, r) {
76
+ if (!r) return e.getUint32(n, !0);
77
+ const s = e.getBigUint64(n, !0);
78
+ if (s > BigInt(Number.MAX_SAFE_INTEGER)) throw new Error("FBX offset exceeds the safe integer range.");
79
+ return Number(s);
80
+ }
81
+ function $(e, n) {
82
+ const r = String.fromCharCode(e.getUint8(n));
83
+ switch (r) {
84
+ case "Y":
85
+ return { value: e.getInt16(n + 1, !0), nextOffset: n + 3 };
86
+ case "C":
87
+ return { value: e.getUint8(n + 1) !== 0, nextOffset: n + 2 };
88
+ case "I":
89
+ return { value: e.getInt32(n + 1, !0), nextOffset: n + 5 };
90
+ case "F":
91
+ return { value: e.getFloat32(n + 1, !0), nextOffset: n + 5 };
92
+ case "D":
93
+ return { value: e.getFloat64(n + 1, !0), nextOffset: n + 9 };
94
+ case "L":
95
+ return { value: Number(e.getBigInt64(n + 1, !0)), nextOffset: n + 9 };
96
+ case "S":
97
+ case "R": {
98
+ const s = e.getUint32(n + 1, !0), t = n + 5, i = new Uint8Array(e.buffer, e.byteOffset + t, s);
99
+ return { value: r === "S" ? new TextDecoder().decode(i) : i, nextOffset: t + s };
100
+ }
101
+ case "f":
102
+ case "d":
103
+ case "l":
104
+ case "i":
105
+ case "b":
106
+ case "c": {
107
+ const s = e.getUint32(n + 9, !0);
108
+ return { value: null, nextOffset: n + 13 + s };
109
+ }
110
+ default:
111
+ throw new Error(`Unsupported FBX property type: ${r}`);
112
+ }
113
+ }
114
+ function j(e) {
115
+ const n = /* @__PURE__ */ new Map();
116
+ e.traverse((r) => {
117
+ r instanceof F && (Array.isArray(r.material) ? r.material = r.material.map((s) => S(s, n)) : r.material = S(r.material, n));
118
+ });
119
+ for (const [r, s] of n)
120
+ r !== s && r.dispose();
121
+ }
122
+ function S(e, n) {
123
+ var i, l, a;
124
+ const r = n.get(e);
125
+ if (r) return r;
126
+ if (!(e instanceof m) && !(e instanceof D))
127
+ return n.set(e, e), e;
128
+ const s = e instanceof m ? e : null, t = new L({
129
+ name: e.name,
130
+ color: ((i = e.color) == null ? void 0 : i.clone()) ?? new A(16777215),
131
+ map: e.map,
132
+ emissive: ((l = e.emissive) == null ? void 0 : l.clone()) ?? new A(0),
133
+ emissiveMap: e.emissiveMap,
134
+ emissiveIntensity: e.emissiveIntensity,
135
+ normalMap: e.normalMap,
136
+ normalMapType: e.normalMapType,
137
+ normalScale: (a = e.normalScale) == null ? void 0 : a.clone(),
138
+ bumpMap: e.bumpMap,
139
+ bumpScale: e.bumpScale,
140
+ displacementMap: e.displacementMap,
141
+ displacementScale: e.displacementScale,
142
+ displacementBias: e.displacementBias,
143
+ aoMap: e.aoMap,
144
+ aoMapIntensity: e.aoMapIntensity,
145
+ alphaMap: e.alphaMap,
146
+ opacity: e.opacity,
147
+ transparent: e.transparent,
148
+ alphaTest: e.alphaTest,
149
+ side: e.side,
150
+ depthTest: e.depthTest,
151
+ depthWrite: e.depthWrite,
152
+ vertexColors: e.vertexColors,
153
+ wireframe: e.wireframe,
154
+ flatShading: e.flatShading,
155
+ metalness: 0,
156
+ // Approximate the Blinn-Phong exponent as perceptual roughness. This keeps
157
+ // glossy FBX materials glossy without pretending they carry metalness data.
158
+ roughness: s ? Math.min(1, Math.max(0.04, Math.sqrt(2 / (s.shininess + 2)))) : 1
159
+ });
160
+ return t.userData = { ...e.userData }, t.visible = e.visible, t.toneMapped = e.toneMapped, t.clippingPlanes = e.clippingPlanes, t.clipIntersection = e.clipIntersection, t.clipShadows = e.clipShadows, t.polygonOffset = e.polygonOffset, t.polygonOffsetFactor = e.polygonOffsetFactor, t.polygonOffsetUnits = e.polygonOffsetUnits, t.dithering = e.dithering, t.needsUpdate = !0, n.set(e, t), t;
161
+ }
162
+ const k = new w().setFromAxisAngle(new B(1, 0, 0), Math.PI / 2), Q = new w();
163
+ function U(e) {
164
+ return e === "fbx" ? "y" : "z";
165
+ }
166
+ function K(e) {
167
+ return e === "y" ? k : Q;
168
+ }
169
+ function I(e, n) {
170
+ const r = K(n);
171
+ return r.equals(e.quaternion) ? !1 : (e.quaternion.copy(r), e.updateMatrixWorld(!0), !0);
172
+ }
173
+ function re(e, n) {
174
+ let r = !1, s = null, t = null, i = "z", l;
175
+ const a = () => {
176
+ r = !0;
177
+ }, u = (async () => {
178
+ i = n.options.upAxis ?? U(n.format);
179
+ let o = null;
180
+ try {
181
+ if (r || n.signal.aborted) throw x();
182
+ const c = e.url ?? await e.resolve(e.entryPath ?? "");
183
+ if (o = new E(), o.name = "viewer-model-root", n.format === "fbx") {
184
+ const d = await fetch(c, { signal: n.signal });
185
+ if (!d.ok) throw new Error(`FBX request failed: ${d.status} ${d.statusText}`);
186
+ const p = await d.arrayBuffer();
187
+ i = n.options.upAxis ?? V(p) ?? U(n.format), o.add(new G().parse(p, C.extractUrlBase(c))), j(o);
188
+ } else {
189
+ const d = new v(), p = new _();
190
+ try {
191
+ n.options.dracoDecoderPath && d.setDecoderPath(n.options.dracoDecoderPath), p.setDRACOLoader(d);
192
+ const h = await p.loadAsync(c), y = h.scenes.filter((g) => g !== h.scene);
193
+ if (o.add(h.scene), r || n.signal.aborted) {
194
+ for (const g of y) o.add(g);
195
+ throw x();
196
+ }
197
+ } finally {
198
+ d.dispose();
199
+ }
200
+ }
201
+ if (r || n.signal.aborted) throw x();
202
+ I(o, i), Z(o);
203
+ const f = new R(o, n.options.simulationTheme, void 0, "model");
204
+ s = o, t = f, o = null;
205
+ } finally {
206
+ o && O(o);
207
+ }
208
+ })();
209
+ return u.catch(() => {
210
+ }), {
211
+ id: `model-${Math.random().toString(36).slice(2)}`,
212
+ opened: u,
213
+ get root() {
214
+ return (t == null ? void 0 : t.root) ?? null;
215
+ },
216
+ getFitBounds() {
217
+ return (t == null ? void 0 : t.getModelBounds()) ?? null;
218
+ },
219
+ beforeRender(o, c, f) {
220
+ return t == null ? void 0 : t.beforeRender(o, c, f);
221
+ },
222
+ capabilities: { canPlay: !1, canPause: !1, canReset: !1, hasPhysics: !1, canReorientUpAxis: !0, canToggleGround: !0 },
223
+ // Session presentation overrides are only consulted for `interactionMode`
224
+ // and `fitPolicy`; the UI group comes from the loader descriptor. Keep the
225
+ // two in sync so the override never reads as a contradictory value.
226
+ presentation: { preferredViewMode: "3d", interactionMode: "object", uiProfile: "model", fitPolicy: "fit-on-ready" },
227
+ getUpAxis() {
228
+ return s ? i : null;
229
+ },
230
+ setTheme(o) {
231
+ t == null || t.setTheme(o);
232
+ },
233
+ setGroundVisible(o) {
234
+ t == null || t.setGroundVisible(o);
235
+ },
236
+ getGroundVisible() {
237
+ return (t == null ? void 0 : t.getGroundVisible()) ?? !0;
238
+ },
239
+ setUpAxis(o) {
240
+ if (r || !s || !t || !I(s, o)) return;
241
+ i = o, t.reseatGround();
242
+ const c = t.getModelBounds();
243
+ n.requestRender(), n.fit(c, { padding: 1.15 });
244
+ },
245
+ beginDispose: a,
246
+ dispose() {
247
+ return a(), l ?? (l = u.catch(() => {
248
+ }).then(() => {
249
+ t == null || t.dispose(), t = null, s && O(s), s = null;
250
+ })), l;
251
+ }
252
+ };
253
+ }
254
+ function Z(e) {
255
+ e.updateMatrixWorld(!0);
256
+ const n = new N().setFromObject(e);
257
+ if (n.isEmpty()) throw new Error("模型不包含可显示的几何内容。");
258
+ const r = n.getSize(new B()), s = Math.max(r.x, r.y, r.z);
259
+ if (!Number.isFinite(s) || s <= 0) throw new Error("模型尺寸无效。");
260
+ }
261
+ function x() {
262
+ return Object.assign(new Error("Model load aborted."), { name: "AbortError", code: "E_ABORTED" });
263
+ }
264
+ export {
265
+ re as createModelSession
266
+ };