@treasuryspatial/viewer-kit 0.2.31

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.
Files changed (80) hide show
  1. package/README.md +5 -0
  2. package/dist/camera.d.ts +28 -0
  3. package/dist/camera.d.ts.map +1 -0
  4. package/dist/camera.js +63 -0
  5. package/dist/engine/ViewerEngine.d.ts +41 -0
  6. package/dist/engine/ViewerEngine.d.ts.map +1 -0
  7. package/dist/engine/ViewerEngine.js +344 -0
  8. package/dist/engine/createViewer.d.ts +3 -0
  9. package/dist/engine/createViewer.d.ts.map +1 -0
  10. package/dist/engine/createViewer.js +7 -0
  11. package/dist/engine/types.d.ts +284 -0
  12. package/dist/engine/types.d.ts.map +1 -0
  13. package/dist/engine/types.js +1 -0
  14. package/dist/exports/download.d.ts +2 -0
  15. package/dist/exports/download.d.ts.map +1 -0
  16. package/dist/exports/download.js +10 -0
  17. package/dist/exports/three-export.d.ts +4 -0
  18. package/dist/exports/three-export.d.ts.map +1 -0
  19. package/dist/exports/three-export.js +25 -0
  20. package/dist/index.d.ts +18 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +12 -0
  23. package/dist/materials/architectural.d.ts +7 -0
  24. package/dist/materials/architectural.d.ts.map +1 -0
  25. package/dist/materials/architectural.js +187 -0
  26. package/dist/materials/catalogue-data.d.ts +149 -0
  27. package/dist/materials/catalogue-data.d.ts.map +1 -0
  28. package/dist/materials/catalogue-data.js +158 -0
  29. package/dist/materials/catalogue.d.ts +5 -0
  30. package/dist/materials/catalogue.d.ts.map +1 -0
  31. package/dist/materials/catalogue.js +23 -0
  32. package/dist/materials/presets.d.ts +21 -0
  33. package/dist/materials/presets.d.ts.map +1 -0
  34. package/dist/materials/presets.js +217 -0
  35. package/dist/materials/renderMetadata.d.ts +15 -0
  36. package/dist/materials/renderMetadata.d.ts.map +1 -0
  37. package/dist/materials/renderMetadata.js +64 -0
  38. package/dist/materials/types.d.ts +50 -0
  39. package/dist/materials/types.d.ts.map +1 -0
  40. package/dist/materials/types.js +1 -0
  41. package/dist/presets/defaults.d.ts +4 -0
  42. package/dist/presets/defaults.d.ts.map +1 -0
  43. package/dist/presets/defaults.js +45 -0
  44. package/dist/presets/sciencePresets.d.ts +7 -0
  45. package/dist/presets/sciencePresets.d.ts.map +1 -0
  46. package/dist/presets/sciencePresets.js +478 -0
  47. package/dist/sky/scienceSky.d.ts +10 -0
  48. package/dist/sky/scienceSky.d.ts.map +1 -0
  49. package/dist/sky/scienceSky.js +85 -0
  50. package/dist/systems/debugSystem.d.ts +12 -0
  51. package/dist/systems/debugSystem.d.ts.map +1 -0
  52. package/dist/systems/debugSystem.js +283 -0
  53. package/dist/systems/environmentSystem.d.ts +31 -0
  54. package/dist/systems/environmentSystem.d.ts.map +1 -0
  55. package/dist/systems/environmentSystem.js +277 -0
  56. package/dist/systems/lightingSystem.d.ts +15 -0
  57. package/dist/systems/lightingSystem.d.ts.map +1 -0
  58. package/dist/systems/lightingSystem.js +100 -0
  59. package/dist/systems/postfxSystem.d.ts +31 -0
  60. package/dist/systems/postfxSystem.d.ts.map +1 -0
  61. package/dist/systems/postfxSystem.js +220 -0
  62. package/dist/systems/rendererSystem.d.ts +4 -0
  63. package/dist/systems/rendererSystem.d.ts.map +1 -0
  64. package/dist/systems/rendererSystem.js +42 -0
  65. package/dist/uploads/grasshopper.d.ts +13 -0
  66. package/dist/uploads/grasshopper.d.ts.map +1 -0
  67. package/dist/uploads/grasshopper.js +182 -0
  68. package/dist/uploads/index.d.ts +8 -0
  69. package/dist/uploads/index.d.ts.map +1 -0
  70. package/dist/uploads/index.js +29 -0
  71. package/dist/uploads/mesh.d.ts +8 -0
  72. package/dist/uploads/mesh.d.ts.map +1 -0
  73. package/dist/uploads/mesh.js +109 -0
  74. package/dist/uploads/rhino3dm.d.ts +7 -0
  75. package/dist/uploads/rhino3dm.d.ts.map +1 -0
  76. package/dist/uploads/rhino3dm.js +33 -0
  77. package/dist/uploads/types.d.ts +68 -0
  78. package/dist/uploads/types.d.ts.map +1 -0
  79. package/dist/uploads/types.js +1 -0
  80. package/package.json +38 -0
package/README.md ADDED
@@ -0,0 +1,5 @@
1
+ # @treasuryspatial/viewer-kit
2
+
3
+ Framework-agnostic viewer core for Composer products.
4
+
5
+ This package will host the stable viewer API and “systems” (renderer/environment/lighting/postfx/debug).
@@ -0,0 +1,28 @@
1
+ import * as THREE from "three";
2
+ export type CameraFrameMode = "iso" | "interior";
3
+ export type CameraFrameConfig = {
4
+ mode?: CameraFrameMode;
5
+ padding?: number;
6
+ direction?: [number, number, number];
7
+ };
8
+ export type CameraNormalizationConfig = {
9
+ recenter?: boolean;
10
+ floorToZero?: boolean;
11
+ normalizePlan?: {
12
+ target?: number;
13
+ maxScale?: number;
14
+ };
15
+ };
16
+ export type CameraNormalizationResult = {
17
+ bounds: THREE.Box3;
18
+ center: THREE.Vector3;
19
+ size: THREE.Vector3;
20
+ scale: number;
21
+ offset: THREE.Vector3;
22
+ };
23
+ export declare const normalizeObject3d: (object: THREE.Object3D, config?: CameraNormalizationConfig) => CameraNormalizationResult;
24
+ export declare const computeCameraFrame: (bounds: THREE.Box3, fovDegrees: number, config?: CameraFrameConfig) => {
25
+ position: THREE.Vector3;
26
+ target: THREE.Vector3;
27
+ };
28
+ //# sourceMappingURL=camera.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"camera.d.ts","sourceRoot":"","sources":["../src/camera.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG,UAAU,CAAC;AAEjD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,IAAI,CAAC,EAAE,eAAe,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,aAAa,CAAC,EAAE;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,CAAC;CACH,CAAC;AAEF,MAAM,MAAM,yBAAyB,GAAG;IACtC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC;IACnB,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC;IACtB,IAAI,EAAE,KAAK,CAAC,OAAO,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,KAAK,CAAC,OAAO,CAAC;CACvB,CAAC;AAKF,eAAO,MAAM,iBAAiB,GAC5B,QAAQ,KAAK,CAAC,QAAQ,EACtB,SAAS,yBAAyB,KACjC,yBA8CF,CAAC;AAEF,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,KAAK,CAAC,IAAI,EAClB,YAAY,MAAM,EAClB,SAAS,iBAAiB,KACzB;IAAE,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC;IAAC,MAAM,EAAE,KAAK,CAAC,OAAO,CAAA;CAoBlD,CAAC"}
package/dist/camera.js ADDED
@@ -0,0 +1,63 @@
1
+ import * as THREE from "three";
2
+ const DEFAULT_FRAME_PADDING = 1.22;
3
+ const DEFAULT_FRAME_DIRECTION = [1, 0.7, 1];
4
+ export const normalizeObject3d = (object, config) => {
5
+ const resolved = config ?? {};
6
+ const offset = new THREE.Vector3();
7
+ let scale = 1;
8
+ object.updateMatrixWorld(true);
9
+ let bounds = new THREE.Box3().setFromObject(object);
10
+ let size = new THREE.Vector3();
11
+ let center = new THREE.Vector3();
12
+ bounds.getSize(size);
13
+ bounds.getCenter(center);
14
+ const targetPlan = resolved.normalizePlan?.target;
15
+ if (typeof targetPlan === "number" && Number.isFinite(targetPlan) && targetPlan > 0) {
16
+ const planMax = Math.max(size.x, size.z);
17
+ if (planMax > 1e-6) {
18
+ const maxScale = resolved.normalizePlan?.maxScale ?? 3;
19
+ scale = Math.min(maxScale, targetPlan / planMax);
20
+ if (Number.isFinite(scale) && scale > 0 && scale !== 1) {
21
+ object.scale.multiplyScalar(scale);
22
+ object.updateMatrixWorld(true);
23
+ bounds = new THREE.Box3().setFromObject(object);
24
+ bounds.getSize(size);
25
+ bounds.getCenter(center);
26
+ }
27
+ }
28
+ }
29
+ if (resolved.recenter) {
30
+ offset.x -= center.x;
31
+ offset.z -= center.z;
32
+ }
33
+ if (resolved.floorToZero) {
34
+ offset.y -= bounds.min.y;
35
+ }
36
+ if (offset.lengthSq() > 0) {
37
+ object.position.add(offset);
38
+ object.updateMatrixWorld(true);
39
+ bounds = new THREE.Box3().setFromObject(object);
40
+ bounds.getSize(size);
41
+ bounds.getCenter(center);
42
+ }
43
+ return { bounds, center, size, scale, offset };
44
+ };
45
+ export const computeCameraFrame = (bounds, fovDegrees, config) => {
46
+ const size = new THREE.Vector3();
47
+ const center = new THREE.Vector3();
48
+ bounds.getSize(size);
49
+ bounds.getCenter(center);
50
+ const mode = config?.mode ?? "iso";
51
+ if (mode === "interior") {
52
+ const position = new THREE.Vector3(center.x, center.y + size.y * 0.2, center.z + size.z * 0.2);
53
+ return { position, target: center };
54
+ }
55
+ const padding = config?.padding ?? DEFAULT_FRAME_PADDING;
56
+ const direction = new THREE.Vector3(...(config?.direction ?? DEFAULT_FRAME_DIRECTION)).normalize();
57
+ const sphere = bounds.getBoundingSphere(new THREE.Sphere());
58
+ const radius = Math.max(0.5, sphere.radius);
59
+ const vFov = THREE.MathUtils.degToRad(fovDegrees);
60
+ const distance = (radius / Math.sin(vFov / 2)) * padding;
61
+ const position = center.clone().add(direction.multiplyScalar(distance));
62
+ return { position, target: center };
63
+ };
@@ -0,0 +1,41 @@
1
+ import type { CameraState, RenderPresetDefinition, ViewerCreateOptions, ViewerHandle, ViewerSnapshot } from "./types";
2
+ export declare class ViewerEngine {
3
+ private static readonly RUNTIME_PRESET_ID;
4
+ private container;
5
+ private renderer;
6
+ private scene;
7
+ private camera;
8
+ private controls;
9
+ private rootGroup;
10
+ private sceneBounds;
11
+ private environmentSystem;
12
+ private lightingSystem;
13
+ private postFxSystem;
14
+ private debugSystem;
15
+ private usePostprocessing;
16
+ private presets;
17
+ private presetId;
18
+ private running;
19
+ private animationFrame;
20
+ private lastWidth;
21
+ private lastHeight;
22
+ private maxPixelRatio;
23
+ constructor(options: ViewerCreateOptions);
24
+ getHandle(): ViewerHandle;
25
+ private invalidate;
26
+ start(): void;
27
+ stop(): void;
28
+ resize(): void;
29
+ dispose(): void;
30
+ setPreset(presetId: string): void;
31
+ setRuntimePreset(preset: RenderPresetDefinition): void;
32
+ setSceneRoot(root: unknown): void;
33
+ setCamera(state: Partial<CameraState>): void;
34
+ getCameraState(): CameraState;
35
+ capturePng(): Promise<Blob>;
36
+ getSnapshot(): ViewerSnapshot;
37
+ private getDebugInfo;
38
+ private applyPreset;
39
+ private resolvePreset;
40
+ }
41
+ //# sourceMappingURL=ViewerEngine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ViewerEngine.d.ts","sourceRoot":"","sources":["../../src/engine/ViewerEngine.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAiB,WAAW,EAAiB,sBAAsB,EAAE,mBAAmB,EAAmB,YAAY,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAQrK,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,iBAAiB,CAAiB;IAE1D,OAAO,CAAC,SAAS,CAAc;IAC/B,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,QAAQ,CAAgB;IAChC,OAAO,CAAC,SAAS,CAAc;IAC/B,OAAO,CAAC,WAAW,CAA2B;IAE9C,OAAO,CAAC,iBAAiB,CAAoB;IAC7C,OAAO,CAAC,cAAc,CAAiB;IACvC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,iBAAiB,CAAU;IAEnC,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,cAAc,CAAuB;IAC7C,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,aAAa,CAAS;gBAElB,OAAO,EAAE,mBAAmB;IAwDxC,SAAS,IAAI,YAAY;IAmBzB,OAAO,CAAC,UAAU;IAKlB,KAAK,IAAI,IAAI;IAgBb,IAAI,IAAI,IAAI;IAQZ,MAAM,IAAI,IAAI;IAcd,OAAO,IAAI,IAAI;IAef,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAIjC,gBAAgB,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI;IAStD,YAAY,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IA6CjC,SAAS,CAAC,KAAK,EAAE,OAAO,CAAC,WAAW,CAAC,GAAG,IAAI;IAuB5C,cAAc,IAAI,WAAW;IAUvB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAmCjC,WAAW,IAAI,cAAc;IAe7B,OAAO,CAAC,YAAY;IAuCpB,OAAO,CAAC,WAAW;IAyBnB,OAAO,CAAC,aAAa;CAKtB"}
@@ -0,0 +1,344 @@
1
+ import * as THREE from "three";
2
+ import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
3
+ import { DEFAULT_PRESET_ID, DEFAULT_PRESETS } from "../presets/defaults";
4
+ import { applyRendererConfig } from "../systems/rendererSystem";
5
+ import { EnvironmentSystem } from "../systems/environmentSystem";
6
+ import { LightingSystem } from "../systems/lightingSystem";
7
+ import { PostFxSystem } from "../systems/postfxSystem";
8
+ import { DebugSystem } from "../systems/debugSystem";
9
+ export class ViewerEngine {
10
+ static RUNTIME_PRESET_ID = "__runtime__";
11
+ container;
12
+ renderer;
13
+ scene;
14
+ camera;
15
+ controls;
16
+ rootGroup;
17
+ sceneBounds = null;
18
+ environmentSystem;
19
+ lightingSystem;
20
+ postFxSystem;
21
+ debugSystem;
22
+ usePostprocessing;
23
+ presets;
24
+ presetId;
25
+ running = false;
26
+ animationFrame = null;
27
+ lastWidth;
28
+ lastHeight;
29
+ maxPixelRatio;
30
+ constructor(options) {
31
+ this.container = options.container;
32
+ const basePresets = options.presets ?? DEFAULT_PRESETS;
33
+ this.presets = { ...basePresets };
34
+ this.presetId = options.presetId ?? DEFAULT_PRESET_ID;
35
+ const width = this.container.clientWidth || 1;
36
+ const height = this.container.clientHeight || 1;
37
+ this.scene = new THREE.Scene();
38
+ this.camera = new THREE.PerspectiveCamera(options.camera?.fov ?? 60, width / height, options.camera?.near ?? 0.1, options.camera?.far ?? 500);
39
+ const camPos = options.camera?.position ?? [15, 12, 15];
40
+ this.camera.position.set(...camPos);
41
+ const rendererOptions = {
42
+ antialias: options.renderer?.antialias ?? true,
43
+ alpha: options.renderer?.alpha ?? false,
44
+ preserveDrawingBuffer: options.renderer?.preserveDrawingBuffer ?? false,
45
+ };
46
+ this.renderer = new THREE.WebGLRenderer(rendererOptions);
47
+ this.renderer.setSize(width, height);
48
+ this.container.appendChild(this.renderer.domElement);
49
+ this.controls = new OrbitControls(this.camera, this.renderer.domElement);
50
+ this.controls.enableDamping = true;
51
+ this.controls.dampingFactor = 0.05;
52
+ this.controls.addEventListener("change", () => {
53
+ if (this.usePostprocessing) {
54
+ this.postFxSystem.invalidate();
55
+ }
56
+ });
57
+ const target = options.camera?.target ?? [0, 1.5, 0];
58
+ this.controls.target.set(...target);
59
+ this.controls.update();
60
+ this.rootGroup = new THREE.Group();
61
+ this.scene.add(this.rootGroup);
62
+ this.environmentSystem = new EnvironmentSystem(this.scene, this.renderer, options.assetResolver);
63
+ this.lightingSystem = new LightingSystem(this.scene);
64
+ this.postFxSystem = new PostFxSystem(this.renderer, this.scene, this.camera);
65
+ this.debugSystem = new DebugSystem(this.scene);
66
+ this.usePostprocessing = options.usePostprocessing ?? true;
67
+ this.lastWidth = width;
68
+ this.lastHeight = height;
69
+ this.maxPixelRatio = 1.75;
70
+ this.postFxSystem.resize(width, height);
71
+ this.applyPreset(this.presetId);
72
+ }
73
+ getHandle() {
74
+ return {
75
+ start: () => this.start(),
76
+ stop: () => this.stop(),
77
+ resize: () => this.resize(),
78
+ invalidate: () => this.invalidate(),
79
+ dispose: () => this.dispose(),
80
+ setPreset: (presetId) => this.setPreset(presetId),
81
+ setRuntimePreset: (preset) => this.setRuntimePreset(preset),
82
+ setSceneRoot: (root) => this.setSceneRoot(root),
83
+ setCamera: (state) => this.setCamera(state),
84
+ getCameraState: () => this.getCameraState(),
85
+ capturePng: () => this.capturePng(),
86
+ getSnapshot: () => this.getSnapshot(),
87
+ getDebugInfo: () => this.getDebugInfo(),
88
+ getCanvas: () => this.renderer.domElement,
89
+ };
90
+ }
91
+ invalidate() {
92
+ this.postFxSystem.invalidate();
93
+ this.start();
94
+ }
95
+ start() {
96
+ if (this.running)
97
+ return;
98
+ this.running = true;
99
+ const loop = () => {
100
+ if (!this.running)
101
+ return;
102
+ this.controls.update();
103
+ if (this.usePostprocessing) {
104
+ this.postFxSystem.render();
105
+ }
106
+ else {
107
+ this.renderer.render(this.scene, this.camera);
108
+ }
109
+ this.animationFrame = requestAnimationFrame(loop);
110
+ };
111
+ this.animationFrame = requestAnimationFrame(loop);
112
+ }
113
+ stop() {
114
+ this.running = false;
115
+ if (this.animationFrame !== null) {
116
+ cancelAnimationFrame(this.animationFrame);
117
+ this.animationFrame = null;
118
+ }
119
+ }
120
+ resize() {
121
+ const width = this.container.clientWidth || 1;
122
+ const height = this.container.clientHeight || 1;
123
+ this.lastWidth = width;
124
+ this.lastHeight = height;
125
+ const deviceRatio = typeof window !== "undefined" ? window.devicePixelRatio : 1;
126
+ this.renderer.setPixelRatio(Math.min(deviceRatio, this.maxPixelRatio));
127
+ this.camera.aspect = width / height;
128
+ this.camera.updateProjectionMatrix();
129
+ this.renderer.setSize(width, height);
130
+ this.postFxSystem.resize(width, height);
131
+ this.postFxSystem.invalidate();
132
+ }
133
+ dispose() {
134
+ this.stop();
135
+ this.environmentSystem.dispose();
136
+ this.lightingSystem.dispose();
137
+ this.postFxSystem.dispose();
138
+ this.debugSystem.dispose();
139
+ this.scene.clear();
140
+ this.controls.dispose();
141
+ this.renderer.dispose();
142
+ if (this.renderer.domElement.parentElement === this.container) {
143
+ this.container.removeChild(this.renderer.domElement);
144
+ }
145
+ }
146
+ setPreset(presetId) {
147
+ this.applyPreset(presetId);
148
+ }
149
+ setRuntimePreset(preset) {
150
+ const normalized = {
151
+ ...preset,
152
+ id: ViewerEngine.RUNTIME_PRESET_ID,
153
+ };
154
+ this.presets[ViewerEngine.RUNTIME_PRESET_ID] = normalized;
155
+ this.applyPreset(ViewerEngine.RUNTIME_PRESET_ID);
156
+ }
157
+ setSceneRoot(root) {
158
+ this.rootGroup.clear();
159
+ const object3d = root;
160
+ if (object3d && object3d.isObject3D) {
161
+ this.rootGroup.add(object3d);
162
+ const bounds = new THREE.Box3().setFromObject(object3d);
163
+ this.sceneBounds = bounds.clone();
164
+ if (!bounds.isEmpty()) {
165
+ const size = new THREE.Vector3();
166
+ bounds.getSize(size);
167
+ const maxDim = Math.max(size.x, size.y, size.z);
168
+ if (Number.isFinite(maxDim) && maxDim > 0) {
169
+ const target = this.controls.target.clone();
170
+ const distance = this.camera.position.distanceTo(target);
171
+ const sphere = new THREE.Sphere();
172
+ bounds.getBoundingSphere(sphere);
173
+ const safetyFar = 1.15;
174
+ const safetyNear = 1.6;
175
+ let near = Math.max(0.01, distance - sphere.radius * safetyNear);
176
+ const maxNear = distance * 0.1;
177
+ if (Number.isFinite(maxNear) && maxNear > 0) {
178
+ near = Math.min(near, maxNear);
179
+ }
180
+ const minSkyFar = 350;
181
+ const far = Math.max(distance + sphere.radius * safetyFar, near + sphere.radius * 2, minSkyFar);
182
+ let needsUpdate = false;
183
+ if (Number.isFinite(near) && this.camera.near !== near) {
184
+ this.camera.near = near;
185
+ needsUpdate = true;
186
+ }
187
+ if (Number.isFinite(far) && this.camera.far !== far) {
188
+ this.camera.far = far;
189
+ needsUpdate = true;
190
+ }
191
+ if (needsUpdate) {
192
+ this.camera.updateProjectionMatrix();
193
+ }
194
+ }
195
+ }
196
+ }
197
+ this.postFxSystem.invalidate();
198
+ // Ensure the render loop is running after a scene update.
199
+ this.start();
200
+ }
201
+ setCamera(state) {
202
+ if (state.position) {
203
+ this.camera.position.set(...state.position);
204
+ }
205
+ if (state.target) {
206
+ this.controls.target.set(...state.target);
207
+ }
208
+ if (typeof state.fov === "number") {
209
+ this.camera.fov = state.fov;
210
+ this.camera.updateProjectionMatrix();
211
+ }
212
+ if (typeof state.near === "number" && Number.isFinite(state.near)) {
213
+ this.camera.near = state.near;
214
+ this.camera.updateProjectionMatrix();
215
+ }
216
+ if (typeof state.far === "number" && Number.isFinite(state.far)) {
217
+ this.camera.far = state.far;
218
+ this.camera.updateProjectionMatrix();
219
+ }
220
+ this.controls.update();
221
+ this.postFxSystem.invalidate();
222
+ }
223
+ getCameraState() {
224
+ return {
225
+ position: [this.camera.position.x, this.camera.position.y, this.camera.position.z],
226
+ target: [this.controls.target.x, this.controls.target.y, this.controls.target.z],
227
+ fov: this.camera.fov,
228
+ near: this.camera.near,
229
+ far: this.camera.far,
230
+ };
231
+ }
232
+ async capturePng() {
233
+ const width = this.renderer.domElement.width || 1;
234
+ const height = this.renderer.domElement.height || 1;
235
+ const target = new THREE.WebGLRenderTarget(width, height);
236
+ const previousTarget = this.renderer.getRenderTarget();
237
+ this.renderer.setRenderTarget(target);
238
+ // Render without postfx for now (upgrade later)
239
+ this.renderer.render(this.scene, this.camera);
240
+ const buffer = new Uint8Array(width * height * 4);
241
+ this.renderer.readRenderTargetPixels(target, 0, 0, width, height, buffer);
242
+ this.renderer.setRenderTarget(previousTarget);
243
+ target.dispose();
244
+ const canvas = document.createElement("canvas");
245
+ canvas.width = width;
246
+ canvas.height = height;
247
+ const ctx = canvas.getContext("2d");
248
+ if (!ctx) {
249
+ return new Blob();
250
+ }
251
+ const imageData = ctx.createImageData(width, height);
252
+ const rowSize = width * 4;
253
+ for (let y = 0; y < height; y += 1) {
254
+ const src = (height - 1 - y) * rowSize;
255
+ const dst = y * rowSize;
256
+ imageData.data.set(buffer.subarray(src, src + rowSize), dst);
257
+ }
258
+ ctx.putImageData(imageData, 0, 0);
259
+ return new Promise((resolve) => {
260
+ canvas.toBlob((blob) => resolve(blob ?? new Blob()), "image/png");
261
+ });
262
+ }
263
+ getSnapshot() {
264
+ const envState = this.environmentSystem.getState();
265
+ return {
266
+ capturedAt: new Date().toISOString(),
267
+ presetId: this.presetId,
268
+ renderer: {
269
+ toneMapping: String(this.renderer.toneMapping),
270
+ toneMappingExposure: this.renderer.toneMappingExposure,
271
+ outputColorSpace: String(this.renderer.outputColorSpace),
272
+ },
273
+ environment: envState,
274
+ postfx: this.postFxSystem.getState(),
275
+ };
276
+ }
277
+ getDebugInfo() {
278
+ const envState = this.environmentSystem.getState();
279
+ const postfxState = this.postFxSystem.getState();
280
+ const renderInfo = this.renderer.info.render;
281
+ const clearColor = new THREE.Color();
282
+ this.renderer.getClearColor(clearColor);
283
+ return {
284
+ presetId: this.presetId,
285
+ camera: this.getCameraState(),
286
+ canvas: {
287
+ width: this.renderer.domElement.width,
288
+ height: this.renderer.domElement.height,
289
+ clientWidth: this.renderer.domElement.clientWidth,
290
+ clientHeight: this.renderer.domElement.clientHeight,
291
+ },
292
+ renderer: {
293
+ toneMapping: String(this.renderer.toneMapping),
294
+ toneMappingExposure: this.renderer.toneMappingExposure,
295
+ outputColorSpace: String(this.renderer.outputColorSpace),
296
+ pixelRatio: this.renderer.getPixelRatio(),
297
+ clearColor: `#${clearColor.getHexString()}`,
298
+ clearAlpha: this.renderer.getClearAlpha(),
299
+ },
300
+ renderInfo: {
301
+ calls: renderInfo.calls,
302
+ triangles: renderInfo.triangles,
303
+ lines: renderInfo.lines,
304
+ points: renderInfo.points,
305
+ },
306
+ scene: {
307
+ children: this.scene.children.length,
308
+ rootChildren: this.rootGroup.children.length,
309
+ lights: this.scene.children.filter((child) => child?.isLight).length,
310
+ },
311
+ environment: envState,
312
+ postfx: postfxState,
313
+ };
314
+ }
315
+ applyPreset(presetId) {
316
+ const preset = this.resolvePreset(presetId);
317
+ this.presetId = preset.id;
318
+ const exposureOverride = preset.sky?.mode === "hdr"
319
+ ? preset.sky.hdr?.exposure
320
+ : preset.sky?.mode === "cube"
321
+ ? preset.sky.cube?.exposure
322
+ : undefined;
323
+ const rendererConfig = exposureOverride !== undefined
324
+ ? { ...preset.renderer, toneMappingExposure: exposureOverride }
325
+ : preset.renderer;
326
+ applyRendererConfig(this.renderer, rendererConfig);
327
+ this.maxPixelRatio = rendererConfig?.maxPixelRatio ?? this.maxPixelRatio;
328
+ // applyRendererConfig can change pixel ratio; keep drawing buffer in sync.
329
+ this.renderer.setSize(this.lastWidth, this.lastHeight);
330
+ this.postFxSystem.resize(this.lastWidth, this.lastHeight);
331
+ void this.environmentSystem.apply(preset);
332
+ this.lightingSystem.apply(preset);
333
+ this.postFxSystem.apply(this.usePostprocessing ? preset.postfx : undefined);
334
+ this.postFxSystem.invalidate();
335
+ this.debugSystem.apply(preset, this.sceneBounds);
336
+ }
337
+ resolvePreset(presetId) {
338
+ if (presetId in this.presets)
339
+ return this.presets[presetId];
340
+ if (DEFAULT_PRESET_ID in this.presets)
341
+ return this.presets[DEFAULT_PRESET_ID];
342
+ return Object.values(this.presets)[0] ?? DEFAULT_PRESETS[DEFAULT_PRESET_ID];
343
+ }
344
+ }
@@ -0,0 +1,3 @@
1
+ import type { ViewerCreateOptions, ViewerHandle } from "./types";
2
+ export declare function createViewer(options: ViewerCreateOptions): ViewerHandle;
3
+ //# sourceMappingURL=createViewer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createViewer.d.ts","sourceRoot":"","sources":["../../src/engine/createViewer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAGjE,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,YAAY,CAKvE"}
@@ -0,0 +1,7 @@
1
+ import { ViewerEngine } from "./ViewerEngine";
2
+ export function createViewer(options) {
3
+ const engine = new ViewerEngine(options);
4
+ const handle = engine.getHandle();
5
+ queueMicrotask(() => options.hooks?.onReady?.(handle));
6
+ return handle;
7
+ }