@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
@@ -0,0 +1,277 @@
1
+ import * as THREE from "three";
2
+ import { HDRLoader } from "three/examples/jsm/loaders/HDRLoader.js";
3
+ export class EnvironmentSystem {
4
+ scene;
5
+ renderer;
6
+ assetResolver;
7
+ pmremGenerator = null;
8
+ currentObjects = [];
9
+ currentBackground = null;
10
+ currentEnvironment = null;
11
+ objectUrls = [];
12
+ loadToken = 0;
13
+ state = { mode: "none", backgroundEnabled: false, lightingEnabled: false };
14
+ fallbackGradient = {
15
+ topColor: "#f8fafc",
16
+ horizonColor: "#e2e8f0",
17
+ sunPosition: [0.3, 0.8, 0.4],
18
+ sunIntensity: 0.12,
19
+ sunSize: 0.06,
20
+ };
21
+ constructor(scene, renderer, assetResolver) {
22
+ this.scene = scene;
23
+ this.renderer = renderer;
24
+ this.assetResolver = assetResolver;
25
+ }
26
+ getState() {
27
+ return { ...this.state };
28
+ }
29
+ async apply(preset) {
30
+ const sky = preset.sky;
31
+ this.disposeCurrent();
32
+ if (!sky || sky.mode === "none") {
33
+ this.scene.background = null;
34
+ this.scene.environment = null;
35
+ this.state = { mode: "none", backgroundEnabled: false, lightingEnabled: false };
36
+ return;
37
+ }
38
+ if (sky.mode === "gradient") {
39
+ this.applyGradientSky(sky.gradient);
40
+ this.state = { mode: "gradient", backgroundEnabled: true, lightingEnabled: false };
41
+ return;
42
+ }
43
+ if (sky.mode === "hdr") {
44
+ try {
45
+ await this.applyHdrSky(sky.hdr);
46
+ }
47
+ catch (error) {
48
+ console.warn("[viewer-kit] HDR sky failed, falling back to gradient.", error);
49
+ this.applyGradientSky(this.fallbackGradient);
50
+ this.state = { mode: "gradient", backgroundEnabled: true, lightingEnabled: false };
51
+ }
52
+ return;
53
+ }
54
+ if (sky.mode === "cube") {
55
+ try {
56
+ await this.applyCubeSky(sky.cube);
57
+ }
58
+ catch (error) {
59
+ console.warn("[viewer-kit] Cube sky failed, falling back to gradient.", error);
60
+ this.applyGradientSky(this.fallbackGradient);
61
+ this.state = { mode: "gradient", backgroundEnabled: true, lightingEnabled: false };
62
+ }
63
+ }
64
+ }
65
+ dispose() {
66
+ this.disposeCurrent();
67
+ if (this.pmremGenerator) {
68
+ this.pmremGenerator.dispose();
69
+ this.pmremGenerator = null;
70
+ }
71
+ }
72
+ disposeCurrent() {
73
+ this.currentObjects.forEach((obj) => {
74
+ if (obj.parent)
75
+ obj.parent.remove(obj);
76
+ if (obj instanceof THREE.Mesh) {
77
+ obj.geometry.dispose();
78
+ const material = obj.material;
79
+ if (Array.isArray(material)) {
80
+ material.forEach((mat) => mat.dispose?.());
81
+ }
82
+ else {
83
+ material.dispose?.();
84
+ }
85
+ }
86
+ });
87
+ this.currentObjects = [];
88
+ if (this.scene.background === this.currentBackground) {
89
+ this.scene.background = null;
90
+ }
91
+ if (this.scene.environment === this.currentEnvironment) {
92
+ this.scene.environment = null;
93
+ }
94
+ const anyScene = this.scene;
95
+ if (anyScene.backgroundRotation?.set)
96
+ anyScene.backgroundRotation.set(0, 0, 0);
97
+ if (anyScene.environmentRotation?.set)
98
+ anyScene.environmentRotation.set(0, 0, 0);
99
+ if (this.currentBackground)
100
+ this.currentBackground.dispose();
101
+ if (this.currentEnvironment && this.currentEnvironment !== this.currentBackground) {
102
+ this.currentEnvironment.dispose();
103
+ }
104
+ this.currentBackground = null;
105
+ this.currentEnvironment = null;
106
+ this.objectUrls.forEach((url) => URL.revokeObjectURL(url));
107
+ this.objectUrls = [];
108
+ }
109
+ applyGradientSky(gradient) {
110
+ const skyGeometry = new THREE.SphereGeometry(300, 64, 64);
111
+ const skyMaterial = new THREE.ShaderMaterial({
112
+ uniforms: {
113
+ topColor: { value: new THREE.Color(gradient.topColor) },
114
+ horizonColor: { value: new THREE.Color(gradient.horizonColor) },
115
+ sunPosition: { value: new THREE.Vector3(...gradient.sunPosition) },
116
+ sunIntensity: { value: gradient.sunIntensity },
117
+ sunSize: { value: gradient.sunSize },
118
+ },
119
+ vertexShader: `
120
+ varying vec3 vPosition;
121
+ void main() {
122
+ vPosition = position;
123
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
124
+ }
125
+ `,
126
+ fragmentShader: `
127
+ uniform vec3 topColor;
128
+ uniform vec3 horizonColor;
129
+ uniform vec3 sunPosition;
130
+ uniform float sunIntensity;
131
+ uniform float sunSize;
132
+ varying vec3 vPosition;
133
+ void main() {
134
+ vec3 direction = normalize(vPosition);
135
+ float h = direction.y;
136
+ float gradientFactor = smoothstep(-0.1, 0.4, h);
137
+ vec3 color = mix(horizonColor, topColor, gradientFactor);
138
+ vec3 sunDir = normalize(sunPosition);
139
+ float sunDist = distance(direction, sunDir);
140
+ float sun = smoothstep(sunSize, 0.0, sunDist) * sunIntensity;
141
+ color = mix(color, topColor, sun);
142
+ gl_FragColor = vec4(color, 1.0);
143
+ }
144
+ `,
145
+ side: THREE.BackSide,
146
+ depthWrite: false,
147
+ });
148
+ const sky = new THREE.Mesh(skyGeometry, skyMaterial);
149
+ sky.renderOrder = -1;
150
+ this.scene.add(sky);
151
+ this.currentObjects.push(sky);
152
+ }
153
+ async resolveAsset(src, kind) {
154
+ if (!this.assetResolver)
155
+ return src;
156
+ const { url, headers } = await this.assetResolver(src, kind);
157
+ if (!headers || Object.keys(headers).length === 0)
158
+ return url;
159
+ const response = await fetch(url, { headers });
160
+ if (!response.ok)
161
+ throw new Error(`Asset fetch failed: ${response.status}`);
162
+ const blob = await response.blob();
163
+ const objectUrl = URL.createObjectURL(blob);
164
+ this.objectUrls.push(objectUrl);
165
+ return objectUrl;
166
+ }
167
+ async applyHdrSky(hdr) {
168
+ const token = ++this.loadToken;
169
+ const backgroundEnabled = hdr.showBackground !== false;
170
+ const lightingEnabled = hdr.showLighting !== false;
171
+ const usePmrem = hdr.usePmrem ?? false;
172
+ const resolved = await this.resolveAsset(hdr.src, "hdr");
173
+ if (token !== this.loadToken)
174
+ return;
175
+ const loader = new HDRLoader();
176
+ const texture = await loader.loadAsync(resolved);
177
+ if (token !== this.loadToken) {
178
+ texture.dispose();
179
+ return;
180
+ }
181
+ texture.mapping = THREE.EquirectangularReflectionMapping;
182
+ texture.colorSpace = THREE.LinearSRGBColorSpace;
183
+ texture.magFilter = THREE.LinearFilter;
184
+ texture.minFilter = THREE.LinearFilter;
185
+ texture.needsUpdate = true;
186
+ texture.center = new THREE.Vector2(0.5, 0.5);
187
+ if (typeof hdr.rotationY === "number") {
188
+ texture.rotation = hdr.rotationY;
189
+ }
190
+ this.applyRotationToScene(hdr.rotation, hdr.rotationY);
191
+ let environmentTexture = null;
192
+ if (lightingEnabled) {
193
+ if (usePmrem) {
194
+ if (!this.pmremGenerator) {
195
+ this.pmremGenerator = new THREE.PMREMGenerator(this.renderer);
196
+ this.pmremGenerator.compileEquirectangularShader();
197
+ }
198
+ environmentTexture = this.pmremGenerator.fromEquirectangular(texture).texture;
199
+ }
200
+ else {
201
+ environmentTexture = texture;
202
+ }
203
+ }
204
+ if (backgroundEnabled) {
205
+ this.scene.background = texture;
206
+ this.currentBackground = texture;
207
+ }
208
+ if (environmentTexture) {
209
+ this.scene.environment = environmentTexture;
210
+ this.currentEnvironment = environmentTexture;
211
+ if (environmentTexture !== texture) {
212
+ this.currentEnvironment = environmentTexture;
213
+ }
214
+ }
215
+ this.state = { mode: "hdr", backgroundEnabled, lightingEnabled };
216
+ }
217
+ async applyCubeSky(cube) {
218
+ const token = ++this.loadToken;
219
+ const backgroundEnabled = cube.showBackground !== false;
220
+ const lightingEnabled = cube.showLighting !== false;
221
+ const usePmrem = cube.usePmrem ?? false;
222
+ const faces = await Promise.all([
223
+ this.resolveAsset(cube.px, "cubeface"),
224
+ this.resolveAsset(cube.nx, "cubeface"),
225
+ this.resolveAsset(cube.py, "cubeface"),
226
+ this.resolveAsset(cube.ny, "cubeface"),
227
+ this.resolveAsset(cube.pz, "cubeface"),
228
+ this.resolveAsset(cube.nz, "cubeface"),
229
+ ]);
230
+ if (token !== this.loadToken)
231
+ return;
232
+ const loader = new THREE.CubeTextureLoader();
233
+ const texture = await loader.loadAsync(faces);
234
+ if (token !== this.loadToken) {
235
+ texture.dispose();
236
+ return;
237
+ }
238
+ texture.colorSpace = THREE.SRGBColorSpace;
239
+ texture.magFilter = THREE.LinearFilter;
240
+ texture.minFilter = THREE.LinearFilter;
241
+ texture.needsUpdate = true;
242
+ this.applyRotationToScene(cube.rotation, cube.rotationY);
243
+ let environmentTexture = null;
244
+ if (lightingEnabled) {
245
+ if (usePmrem) {
246
+ if (!this.pmremGenerator) {
247
+ this.pmremGenerator = new THREE.PMREMGenerator(this.renderer);
248
+ }
249
+ environmentTexture = this.pmremGenerator.fromCubemap(texture).texture;
250
+ }
251
+ else {
252
+ environmentTexture = texture;
253
+ }
254
+ }
255
+ if (backgroundEnabled) {
256
+ this.scene.background = texture;
257
+ this.currentBackground = texture;
258
+ }
259
+ if (environmentTexture) {
260
+ this.scene.environment = environmentTexture;
261
+ this.currentEnvironment = environmentTexture;
262
+ }
263
+ this.state = { mode: "cube", backgroundEnabled, lightingEnabled };
264
+ }
265
+ applyRotationToScene(rotation, rotationY) {
266
+ const yaw = rotation?.yaw ?? rotationY ?? 0;
267
+ const pitch = rotation?.pitch ?? 0;
268
+ const roll = rotation?.roll ?? 0;
269
+ const anyScene = this.scene;
270
+ if (anyScene.backgroundRotation?.set) {
271
+ anyScene.backgroundRotation.set(pitch, yaw, roll);
272
+ }
273
+ if (anyScene.environmentRotation?.set) {
274
+ anyScene.environmentRotation.set(pitch, yaw, roll);
275
+ }
276
+ }
277
+ }
@@ -0,0 +1,15 @@
1
+ import * as THREE from "three";
2
+ import type { RenderPresetDefinition } from "../engine/types";
3
+ export declare class LightingSystem {
4
+ private scene;
5
+ private lights;
6
+ private targets;
7
+ constructor(scene: THREE.Scene);
8
+ apply(preset: RenderPresetDefinition): void;
9
+ dispose(): void;
10
+ private add;
11
+ private addTarget;
12
+ private clear;
13
+ private configureShadow;
14
+ }
15
+ //# sourceMappingURL=lightingSystem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lightingSystem.d.ts","sourceRoot":"","sources":["../../src/systems/lightingSystem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAqB,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAUjF,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,OAAO,CAAwB;gBAE3B,KAAK,EAAE,KAAK,CAAC,KAAK;IAI9B,KAAK,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI;IAuD3C,OAAO,IAAI,IAAI;IAIf,OAAO,CAAC,GAAG;IAKX,OAAO,CAAC,SAAS;IAKjB,OAAO,CAAC,KAAK;IAWb,OAAO,CAAC,eAAe;CAcxB"}
@@ -0,0 +1,100 @@
1
+ import * as THREE from "three";
2
+ const DEFAULT_LIGHTING = {
3
+ rig: "studio",
4
+ ambient: { color: "#ffffff", intensity: 0.8 },
5
+ hemisphere: { skyColor: "#ffffff", groundColor: "#f4f4f4", intensity: 1.0 },
6
+ key: { color: "#ffffff", intensity: 1.4, position: [10, 15, 5], castShadow: true },
7
+ fill: { color: "#ffffff", intensity: 0.5, position: [-10, 10, -5] },
8
+ };
9
+ export class LightingSystem {
10
+ scene;
11
+ lights = [];
12
+ targets = [];
13
+ constructor(scene) {
14
+ this.scene = scene;
15
+ }
16
+ apply(preset) {
17
+ this.clear();
18
+ const config = preset.lighting ?? DEFAULT_LIGHTING;
19
+ const lightTarget = preset.sceneExtras?.lightTarget ?? [0, 1.5, 0];
20
+ if (config.rig === "none")
21
+ return;
22
+ const ambient = config.ambient ?? DEFAULT_LIGHTING.ambient;
23
+ if (ambient && ambient.intensity > 0) {
24
+ const light = new THREE.AmbientLight(new THREE.Color(ambient.color), ambient.intensity);
25
+ this.add(light);
26
+ }
27
+ const hemi = config.hemisphere ?? DEFAULT_LIGHTING.hemisphere;
28
+ if (hemi && hemi.intensity > 0) {
29
+ const light = new THREE.HemisphereLight(new THREE.Color(hemi.skyColor), new THREE.Color(hemi.groundColor), hemi.intensity);
30
+ this.add(light);
31
+ }
32
+ const key = config.key ?? DEFAULT_LIGHTING.key;
33
+ if (key && key.intensity > 0) {
34
+ const light = new THREE.DirectionalLight(new THREE.Color(key.color), key.intensity);
35
+ light.position.set(...key.position);
36
+ light.castShadow = key.castShadow ?? false;
37
+ if (light.castShadow) {
38
+ this.configureShadow(light, config.shadow);
39
+ }
40
+ light.target.position.set(lightTarget[0], lightTarget[1], lightTarget[2]);
41
+ this.addTarget(light.target);
42
+ this.add(light);
43
+ }
44
+ const fill = config.fill ?? DEFAULT_LIGHTING.fill;
45
+ if (fill && fill.intensity > 0) {
46
+ const light = new THREE.DirectionalLight(new THREE.Color(fill.color), fill.intensity);
47
+ light.position.set(...fill.position);
48
+ light.target.position.set(lightTarget[0], lightTarget[1], lightTarget[2]);
49
+ this.addTarget(light.target);
50
+ this.add(light);
51
+ }
52
+ if (preset.interiorLights?.length) {
53
+ preset.interiorLights.forEach((interior) => {
54
+ const color = interior.color ? new THREE.Color(interior.color) : new THREE.Color("#ffffff");
55
+ const distance = typeof interior.distance === "number" ? interior.distance : 40;
56
+ const point = new THREE.PointLight(color, interior.intensity, distance);
57
+ point.position.set(...interior.position);
58
+ this.add(point);
59
+ });
60
+ }
61
+ }
62
+ dispose() {
63
+ this.clear();
64
+ }
65
+ add(light) {
66
+ this.scene.add(light);
67
+ this.lights.push(light);
68
+ }
69
+ addTarget(target) {
70
+ this.scene.add(target);
71
+ this.targets.push(target);
72
+ }
73
+ clear() {
74
+ this.lights.forEach((light) => {
75
+ if (light.parent)
76
+ light.parent.remove(light);
77
+ });
78
+ this.lights = [];
79
+ this.targets.forEach((target) => {
80
+ if (target.parent)
81
+ target.parent.remove(target);
82
+ });
83
+ this.targets = [];
84
+ }
85
+ configureShadow(light, config) {
86
+ if (!light.shadow)
87
+ return;
88
+ const cameraSize = config?.cameraSize ?? 25;
89
+ light.shadow.camera.left = -cameraSize;
90
+ light.shadow.camera.right = cameraSize;
91
+ light.shadow.camera.top = cameraSize;
92
+ light.shadow.camera.bottom = -cameraSize;
93
+ light.shadow.camera.near = config?.near ?? 0.1;
94
+ light.shadow.camera.far = config?.far ?? 60;
95
+ const mapSize = config?.mapSize ?? 2048;
96
+ light.shadow.mapSize.set(mapSize, mapSize);
97
+ light.shadow.bias = config?.bias ?? -0.0001;
98
+ light.shadow.radius = config?.radius ?? 3;
99
+ }
100
+ }
@@ -0,0 +1,31 @@
1
+ import * as THREE from "three";
2
+ import type { PostFxConfig } from "../engine/types";
3
+ export declare class PostFxSystem {
4
+ private renderer;
5
+ private scene;
6
+ private camera;
7
+ private composer;
8
+ private renderPass;
9
+ private aoPass;
10
+ private effectPass;
11
+ private bloomEffect;
12
+ private vignetteEffect;
13
+ private enabled;
14
+ private lastWidth;
15
+ private lastHeight;
16
+ private pendingConfig;
17
+ private currentConfig;
18
+ constructor(renderer: THREE.WebGLRenderer, scene: THREE.Scene, camera: THREE.PerspectiveCamera);
19
+ apply(config?: PostFxConfig): void;
20
+ render(): void;
21
+ resize(width: number, height: number): void;
22
+ private getDrawingBufferSize;
23
+ dispose(): void;
24
+ invalidate(): void;
25
+ getState(): {
26
+ aoEnabled: boolean;
27
+ bloomEnabled: boolean;
28
+ vignetteEnabled: boolean;
29
+ };
30
+ }
31
+ //# sourceMappingURL=postfxSystem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postfxSystem.d.ts","sourceRoot":"","sources":["../../src/systems/postfxSystem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAG/B,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAsB;IACtC,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,MAAM,CAA0B;IACxC,OAAO,CAAC,QAAQ,CAA+B;IAC/C,OAAO,CAAC,UAAU,CAA2B;IAC7C,OAAO,CAAC,MAAM,CAA6B;IAC3C,OAAO,CAAC,UAAU,CAA2B;IAC7C,OAAO,CAAC,WAAW,CAA4B;IAC/C,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,SAAS,CAAK;IACtB,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,aAAa,CAA6B;IAClD,OAAO,CAAC,aAAa,CAA6B;gBAEtC,QAAQ,EAAE,KAAK,CAAC,aAAa,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,iBAAiB;IAM9F,KAAK,CAAC,MAAM,CAAC,EAAE,YAAY,GAAG,IAAI;IAiHlC,MAAM,IAAI,IAAI;IAoBd,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAwB3C,OAAO,CAAC,oBAAoB;IAS5B,OAAO,IAAI,IAAI;IAaf,UAAU,IAAI,IAAI;IAqBlB,QAAQ,IAAI;QAAE,SAAS,EAAE,OAAO,CAAC;QAAC,YAAY,EAAE,OAAO,CAAC;QAAC,eAAe,EAAE,OAAO,CAAA;KAAE;CAOpF"}
@@ -0,0 +1,220 @@
1
+ import * as THREE from "three";
2
+ import { BloomEffect, EffectComposer, EffectPass, RenderPass, VignetteEffect } from "postprocessing";
3
+ import { N8AOPostPass } from "n8ao";
4
+ export class PostFxSystem {
5
+ renderer;
6
+ scene;
7
+ camera;
8
+ composer = null;
9
+ renderPass = null;
10
+ aoPass = null;
11
+ effectPass = null;
12
+ bloomEffect = null;
13
+ vignetteEffect = null;
14
+ enabled = false;
15
+ lastWidth = 0;
16
+ lastHeight = 0;
17
+ pendingConfig = null;
18
+ currentConfig = null;
19
+ constructor(renderer, scene, camera) {
20
+ this.renderer = renderer;
21
+ this.scene = scene;
22
+ this.camera = camera;
23
+ }
24
+ apply(config) {
25
+ const ao = config?.ao;
26
+ const bloom = config?.bloom;
27
+ const vignette = config?.vignette;
28
+ const shouldEnable = Boolean(ao?.enabled || bloom?.enabled || vignette?.enabled);
29
+ this.currentConfig = config ?? null;
30
+ if (!shouldEnable) {
31
+ this.enabled = false;
32
+ this.pendingConfig = null;
33
+ if (this.aoPass)
34
+ this.aoPass.enabled = false;
35
+ if (this.effectPass)
36
+ this.effectPass.enabled = false;
37
+ return;
38
+ }
39
+ this.pendingConfig = config ?? null;
40
+ if (this.lastWidth < 2 || this.lastHeight < 2) {
41
+ this.enabled = false;
42
+ return;
43
+ }
44
+ if (!this.composer) {
45
+ this.composer = new EffectComposer(this.renderer);
46
+ this.renderPass = new RenderPass(this.scene, this.camera);
47
+ this.renderPass.clearPass && (this.renderPass.clearPass.overrideClearAlpha = 1);
48
+ this.composer.addPass(this.renderPass);
49
+ this.composer.setSize(this.lastWidth, this.lastHeight);
50
+ }
51
+ this.composer.autoRenderToScreen = false;
52
+ if (!this.aoPass) {
53
+ const buffer = this.getDrawingBufferSize();
54
+ this.aoPass = new N8AOPostPass(this.scene, this.camera, buffer.width, buffer.height);
55
+ this.composer.addPass(this.aoPass);
56
+ }
57
+ else {
58
+ const buffer = this.getDrawingBufferSize();
59
+ this.aoPass.setSize(buffer.width, buffer.height);
60
+ }
61
+ if (!this.bloomEffect) {
62
+ this.bloomEffect = new BloomEffect({
63
+ intensity: bloom?.intensity ?? 0.0,
64
+ radius: bloom?.radius ?? 0.85,
65
+ luminanceThreshold: bloom?.threshold ?? 1.0,
66
+ luminanceSmoothing: bloom?.smoothing ?? 0.03,
67
+ });
68
+ }
69
+ if (!this.vignetteEffect) {
70
+ this.vignetteEffect = new VignetteEffect({
71
+ offset: vignette?.offset ?? 0.5,
72
+ darkness: vignette?.darkness ?? 0.5,
73
+ });
74
+ }
75
+ const enableEffectPass = Boolean(bloom?.enabled || vignette?.enabled);
76
+ if (enableEffectPass && !this.effectPass) {
77
+ this.effectPass = new EffectPass(this.camera, this.bloomEffect, this.vignetteEffect);
78
+ this.composer.addPass(this.effectPass);
79
+ }
80
+ this.enabled = true;
81
+ this.pendingConfig = null;
82
+ if (this.aoPass) {
83
+ this.aoPass.enabled = Boolean(ao?.enabled);
84
+ if (ao?.intensity !== undefined)
85
+ this.aoPass.configuration.intensity = ao.intensity;
86
+ if (ao?.radius !== undefined)
87
+ this.aoPass.configuration.aoRadius = ao.radius;
88
+ if (ao?.distanceFalloff !== undefined)
89
+ this.aoPass.configuration.distanceFalloff = ao.distanceFalloff;
90
+ if (ao?.halfRes !== undefined)
91
+ this.aoPass.configuration.halfRes = ao.halfRes;
92
+ const quality = (ao?.quality ?? "high").toLowerCase();
93
+ const qualityMode = quality === "low" ? "Low" : quality === "medium" ? "Medium" : "High";
94
+ this.aoPass.setQualityMode(qualityMode);
95
+ if (ao?.color) {
96
+ this.aoPass.configuration.color = new THREE.Color(ao.color);
97
+ }
98
+ this.aoPass.firstFrame?.();
99
+ }
100
+ if (this.bloomEffect) {
101
+ this.bloomEffect.intensity = bloom?.enabled ? bloom?.intensity ?? 1.0 : 0.0;
102
+ if (bloom?.radius !== undefined) {
103
+ this.bloomEffect.mipmapBlurPass.radius = bloom.radius;
104
+ }
105
+ if (bloom?.threshold !== undefined)
106
+ this.bloomEffect.luminanceMaterial.threshold = bloom.threshold;
107
+ if (bloom?.smoothing !== undefined)
108
+ this.bloomEffect.luminanceMaterial.smoothing = bloom.smoothing;
109
+ }
110
+ if (this.vignetteEffect) {
111
+ const offset = vignette?.offset ?? 0.5;
112
+ const darkness = vignette?.enabled ? vignette.darkness ?? 0.5 : 0.0;
113
+ this.vignetteEffect.offset = offset;
114
+ this.vignetteEffect.darkness = darkness;
115
+ }
116
+ if (this.effectPass) {
117
+ this.effectPass.enabled = enableEffectPass;
118
+ }
119
+ // Ensure the last enabled pass renders to screen.
120
+ const passes = [this.renderPass, this.aoPass, this.effectPass].filter(Boolean);
121
+ passes.forEach((pass) => {
122
+ pass.renderToScreen = false;
123
+ });
124
+ const enabledPasses = passes.filter((pass) => pass.enabled);
125
+ const lastPass = enabledPasses[enabledPasses.length - 1];
126
+ if (lastPass) {
127
+ lastPass.renderToScreen = true;
128
+ }
129
+ this.invalidate();
130
+ }
131
+ render() {
132
+ if (this.enabled && this.composer) {
133
+ // EffectComposer disables autoClear; force a clean frame on the default framebuffer.
134
+ this.renderer.setRenderTarget(null);
135
+ this.renderer.clear(true, true, true);
136
+ if (this.aoPass) {
137
+ const aoPassAny = this.aoPass;
138
+ if (aoPassAny.accumulationRenderTarget) {
139
+ const prevTarget = this.renderer.getRenderTarget();
140
+ this.renderer.setRenderTarget(aoPassAny.accumulationRenderTarget);
141
+ this.renderer.clear(true, true, true);
142
+ this.renderer.setRenderTarget(prevTarget);
143
+ }
144
+ }
145
+ this.composer.render();
146
+ return;
147
+ }
148
+ this.renderer.render(this.scene, this.camera);
149
+ }
150
+ resize(width, height) {
151
+ this.lastWidth = width;
152
+ this.lastHeight = height;
153
+ if (this.composer) {
154
+ // Full rebuild to avoid stale postprocessing buffers after resize.
155
+ this.composer.dispose();
156
+ this.composer = null;
157
+ this.renderPass = null;
158
+ this.aoPass = null;
159
+ this.effectPass = null;
160
+ this.enabled = false;
161
+ }
162
+ if (this.currentConfig) {
163
+ const config = this.currentConfig;
164
+ this.currentConfig = null;
165
+ this.apply(config);
166
+ }
167
+ if (this.pendingConfig) {
168
+ const pending = this.pendingConfig;
169
+ this.pendingConfig = null;
170
+ this.apply(pending);
171
+ }
172
+ }
173
+ getDrawingBufferSize() {
174
+ const size = new THREE.Vector2();
175
+ this.renderer.getDrawingBufferSize(size);
176
+ return {
177
+ width: Math.max(1, Math.round(size.x)),
178
+ height: Math.max(1, Math.round(size.y)),
179
+ };
180
+ }
181
+ dispose() {
182
+ if (this.composer) {
183
+ this.composer.dispose();
184
+ }
185
+ this.renderPass = null;
186
+ this.aoPass = null;
187
+ this.effectPass = null;
188
+ this.bloomEffect = null;
189
+ this.vignetteEffect = null;
190
+ this.composer = null;
191
+ this.enabled = false;
192
+ }
193
+ invalidate() {
194
+ if (this.aoPass) {
195
+ this.aoPass.firstFrame?.();
196
+ const aoPassAny = this.aoPass;
197
+ if (aoPassAny.accumulationRenderTarget) {
198
+ const prevTarget = this.renderer.getRenderTarget();
199
+ this.renderer.setRenderTarget(aoPassAny.accumulationRenderTarget);
200
+ this.renderer.clear(true, true, true);
201
+ this.renderer.setRenderTarget(prevTarget);
202
+ }
203
+ }
204
+ if (this.composer) {
205
+ const prevTarget = this.renderer.getRenderTarget();
206
+ this.renderer.setRenderTarget(this.composer.inputBuffer);
207
+ this.renderer.clear(true, true, true);
208
+ this.renderer.setRenderTarget(this.composer.outputBuffer);
209
+ this.renderer.clear(true, true, true);
210
+ this.renderer.setRenderTarget(prevTarget);
211
+ }
212
+ }
213
+ getState() {
214
+ return {
215
+ aoEnabled: Boolean(this.aoPass?.enabled),
216
+ bloomEnabled: Boolean(this.effectPass?.enabled && this.bloomEffect && this.bloomEffect.intensity > 0),
217
+ vignetteEnabled: Boolean(this.effectPass?.enabled && this.vignetteEffect && this.vignetteEffect.darkness > 0),
218
+ };
219
+ }
220
+ }
@@ -0,0 +1,4 @@
1
+ import * as THREE from "three";
2
+ import type { RendererConfig } from "../engine/types";
3
+ export declare function applyRendererConfig(renderer: THREE.WebGLRenderer, config?: RendererConfig): void;
4
+ //# sourceMappingURL=rendererSystem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rendererSystem.d.ts","sourceRoot":"","sources":["../../src/systems/rendererSystem.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAC/B,OAAO,KAAK,EAAE,cAAc,EAAmB,MAAM,iBAAiB,CAAC;AAsBvE,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,KAAK,CAAC,aAAa,EAAE,MAAM,CAAC,EAAE,cAAc,GAAG,IAAI,CAuBhG"}