@vgai/engine 0.2.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.
Files changed (147) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +35 -0
  3. package/package.json +55 -0
  4. package/src/adapter/authoring.ts +402 -0
  5. package/src/adapter/colyseus-networking-adapter.ts +72 -0
  6. package/src/adapter/first-party-systems.ts +103 -0
  7. package/src/adapter/game-adapter.ts +151 -0
  8. package/src/adapter/host-context.ts +77 -0
  9. package/src/adapter/index.ts +85 -0
  10. package/src/adapter/ingest/game-contract.ts +59 -0
  11. package/src/adapter/ingest/overlay-applier.ts +207 -0
  12. package/src/adapter/ingest/overlay-apply.ts +124 -0
  13. package/src/adapter/ingest/overlay-file.ts +126 -0
  14. package/src/adapter/ingest/overlay-report.ts +176 -0
  15. package/src/adapter/ingest/scene-capture.ts +307 -0
  16. package/src/adapter/ingest/upstream-pin.ts +52 -0
  17. package/src/adapter/loop-gate-report.ts +54 -0
  18. package/src/adapter/rapier-physics-adapter.ts +56 -0
  19. package/src/adapter/system-adapter.ts +154 -0
  20. package/src/adapter/transform.ts +18 -0
  21. package/src/adapter/vgai-scene-game-adapter.ts +886 -0
  22. package/src/adapter/world-kind.ts +34 -0
  23. package/src/ai/navigation.ts +164 -0
  24. package/src/animation/anim-graph-types.ts +56 -0
  25. package/src/animation/anim-graph.ts +406 -0
  26. package/src/animation/anim-system.ts +28 -0
  27. package/src/animation/blend-node.ts +119 -0
  28. package/src/animation/property-track.ts +178 -0
  29. package/src/animation/schema.ts +204 -0
  30. package/src/assets.ts +80 -0
  31. package/src/audio/ambient.ts +300 -0
  32. package/src/audio/impacts.ts +212 -0
  33. package/src/audio/index.ts +7 -0
  34. package/src/audio/movement.ts +140 -0
  35. package/src/audio/musical.ts +200 -0
  36. package/src/audio/ui-sounds.ts +171 -0
  37. package/src/audio/vehicle.ts +235 -0
  38. package/src/audio/weapons.ts +152 -0
  39. package/src/core/game-loop.ts +127 -0
  40. package/src/core/system-runner.ts +298 -0
  41. package/src/core/types.ts +58 -0
  42. package/src/dev/console-bridge.ts +83 -0
  43. package/src/dev/debug-draw.ts +80 -0
  44. package/src/dev/logger.ts +119 -0
  45. package/src/ecs/component-manager.ts +748 -0
  46. package/src/ecs/game-component.ts +147 -0
  47. package/src/ecs/hmr-swap-report.ts +65 -0
  48. package/src/input/input-manager.ts +439 -0
  49. package/src/input/input-types.ts +19 -0
  50. package/src/input/schema.ts +129 -0
  51. package/src/loader.ts +70 -0
  52. package/src/manifest/index.ts +24 -0
  53. package/src/manifest/load-file.ts +16 -0
  54. package/src/manifest/load.ts +378 -0
  55. package/src/manifest/schema.ts +375 -0
  56. package/src/physics/collision-system.ts +76 -0
  57. package/src/physics/physics-registry.ts +83 -0
  58. package/src/physics/transform-writer.ts +41 -0
  59. package/src/physics/trigger-dispatch.ts +97 -0
  60. package/src/react/game-state.tsx +172 -0
  61. package/src/render/auto-batcher.ts +169 -0
  62. package/src/render/render-batch-system.ts +268 -0
  63. package/src/render/render-features.ts +146 -0
  64. package/src/render/render-settings.ts +72 -0
  65. package/src/runtime/create-runtime.ts +1152 -0
  66. package/src/runtime/frame-selector-cache.ts +81 -0
  67. package/src/runtime/game.ts +1003 -0
  68. package/src/runtime/input-router.ts +213 -0
  69. package/src/runtime/mount-game.ts +269 -0
  70. package/src/runtime/mount-manifest.ts +361 -0
  71. package/src/runtime/scene-ui-bridge.ts +86 -0
  72. package/src/runtime/scene-ui-data.ts +119 -0
  73. package/src/runtime/state-bridge.ts +79 -0
  74. package/src/runtime/types.ts +196 -0
  75. package/src/scene/asset-loaders.ts +195 -0
  76. package/src/scene/asset-paths.ts +123 -0
  77. package/src/scene/asset-registry.ts +67 -0
  78. package/src/scene/collider-dimensions.ts +125 -0
  79. package/src/scene/component-registry.ts +40 -0
  80. package/src/scene/defaults.ts +164 -0
  81. package/src/scene/geometries/index.ts +7 -0
  82. package/src/scene/geometries/terrain.ts +42 -0
  83. package/src/scene/geometry-registry.ts +42 -0
  84. package/src/scene/instance-registry.ts +84 -0
  85. package/src/scene/instancers/grid.ts +38 -0
  86. package/src/scene/instancers/index.ts +7 -0
  87. package/src/scene/light-camera-factory.ts +97 -0
  88. package/src/scene/material-factory.ts +211 -0
  89. package/src/scene/material-registry.ts +73 -0
  90. package/src/scene/materials/index.ts +7 -0
  91. package/src/scene/materials/water.ts +56 -0
  92. package/src/scene/parse.ts +71 -0
  93. package/src/scene/particles-factory.ts +383 -0
  94. package/src/scene/scene-apply.ts +356 -0
  95. package/src/scene/scene-diff-schema.ts +115 -0
  96. package/src/scene/scene-diff-types.ts +29 -0
  97. package/src/scene/scene-loader.ts +1533 -0
  98. package/src/scene/scene-query.ts +63 -0
  99. package/src/scene/scene-types.ts +34 -0
  100. package/src/scene/scene-version.ts +40 -0
  101. package/src/scene/schema/animation.ts +95 -0
  102. package/src/scene/schema/audio.ts +25 -0
  103. package/src/scene/schema/camera.ts +21 -0
  104. package/src/scene/schema/collider.ts +69 -0
  105. package/src/scene/schema/entity-ref.ts +78 -0
  106. package/src/scene/schema/entity.ts +169 -0
  107. package/src/scene/schema/environment.ts +384 -0
  108. package/src/scene/schema/index.ts +95 -0
  109. package/src/scene/schema/instances.ts +35 -0
  110. package/src/scene/schema/joint.ts +26 -0
  111. package/src/scene/schema/light.ts +38 -0
  112. package/src/scene/schema/material.ts +113 -0
  113. package/src/scene/schema/mesh.ts +108 -0
  114. package/src/scene/schema/particles.ts +398 -0
  115. package/src/scene/schema/physics.ts +49 -0
  116. package/src/scene/schema/scene-file.ts +299 -0
  117. package/src/scene/schema/shadow.ts +24 -0
  118. package/src/scene/schema/spline.ts +21 -0
  119. package/src/scene/schema/tuples.ts +21 -0
  120. package/src/scene/schema/ui.ts +602 -0
  121. package/src/scene/user-data.ts +203 -0
  122. package/src/setup/setup-audio.ts +60 -0
  123. package/src/setup/setup-particles.ts +23 -0
  124. package/src/setup/setup-physics.ts +67 -0
  125. package/src/setup/setup-renderer.ts +529 -0
  126. package/src/types-n8ao.d.ts +37 -0
  127. package/src/types-realism-effects.d.ts +61 -0
  128. package/src/world2d/authoring-2d.ts +208 -0
  129. package/src/world2d/capture-to-scene2d.ts +52 -0
  130. package/src/world2d/collision-2d.ts +106 -0
  131. package/src/world2d/components-2d.ts +86 -0
  132. package/src/world2d/index.ts +66 -0
  133. package/src/world2d/ingest-iframe-2d.ts +255 -0
  134. package/src/world2d/ingest2d.ts +131 -0
  135. package/src/world2d/physics2d-registry.ts +49 -0
  136. package/src/world2d/pixi-game-adapter.ts +325 -0
  137. package/src/world2d/pixi-surface.ts +78 -0
  138. package/src/world2d/scene-capture-2d.ts +117 -0
  139. package/src/world2d/scene2d-loader.ts +308 -0
  140. package/src/world2d/schema/entity2d.ts +145 -0
  141. package/src/world2d/schema/physics2d.ts +53 -0
  142. package/src/world2d/schema/sprite.ts +71 -0
  143. package/src/world2d/schema/tilemap.ts +22 -0
  144. package/src/world2d/schema/tuples2d.ts +25 -0
  145. package/src/world2d/system-adapters-2d.ts +49 -0
  146. package/src/world2d/transform-writer-2d.ts +24 -0
  147. package/src/world2d/types.ts +55 -0
@@ -0,0 +1,529 @@
1
+ import { N8AOPostPass } from 'n8ao';
2
+ import {
3
+ BloomEffect,
4
+ BrightnessContrastEffect,
5
+ ChromaticAberrationEffect,
6
+ ColorDepthEffect,
7
+ DepthOfFieldEffect,
8
+ DotScreenEffect,
9
+ type Effect,
10
+ EffectComposer,
11
+ EffectPass,
12
+ GlitchEffect,
13
+ GodRaysEffect,
14
+ GridEffect,
15
+ HueSaturationEffect,
16
+ LensDistortionEffect,
17
+ NoiseEffect,
18
+ NormalPass,
19
+ OutlineEffect,
20
+ PixelationEffect,
21
+ RenderPass,
22
+ ScanlineEffect,
23
+ SepiaEffect,
24
+ ShockWaveEffect,
25
+ SMAAEffect,
26
+ SMAAPreset,
27
+ SSAOEffect,
28
+ TiltShiftEffect,
29
+ VignetteEffect,
30
+ } from 'postprocessing';
31
+ import { MotionBlurEffect, SSGIEffect, SSREffect, VelocityDepthNormalPass } from 'realism-effects';
32
+ import * as THREE from 'three';
33
+ import { type RenderScope, resolveRenderSettings } from '../render/render-settings';
34
+ import { DEFAULTS } from '../scene/defaults';
35
+ import type { SceneEnvironment } from '../scene/scene-types';
36
+
37
+ export interface RendererContext {
38
+ renderer: THREE.WebGLRenderer;
39
+ scene: THREE.Scene;
40
+ camera: THREE.PerspectiveCamera;
41
+ composer: EffectComposer;
42
+ }
43
+
44
+ /**
45
+ * Apply the live renderer-level render settings (shadows, resolution scale) from
46
+ * the resolved cascade. `antialias`/`backend` are reload-only (renderer re-init)
47
+ * and are handled at construction, not here. Pass the scene `rendering` scope
48
+ * (e.g. `env.rendering`); absent values fall back to the registry defaults so
49
+ * behavior is unchanged when a scene omits `rendering`.
50
+ *
51
+ * `basePixelRatio` is the call-site's native pixel-ratio policy (the runtime caps
52
+ * at 2; the editor uses the raw devicePixelRatio); `resolutionScale` multiplies it.
53
+ */
54
+ export function applyRendererSettings(
55
+ renderer: THREE.WebGLRenderer,
56
+ rendering?: RenderScope,
57
+ basePixelRatio = Math.min(window.devicePixelRatio, 2),
58
+ ): void {
59
+ const settings = resolveRenderSettings(rendering);
60
+ renderer.shadowMap.enabled = Boolean(settings['shadows']);
61
+ const scale = Number(settings['resolutionScale']) || 1;
62
+ renderer.setPixelRatio(basePixelRatio * scale);
63
+ }
64
+
65
+ /**
66
+ * Create ONLY the WebGL renderer. The HOST owns the renderer (see
67
+ * `HostContext.renderer`); the game/adapter owns the scene/camera/composer it
68
+ * renders into. Split out of `setupRenderer` for the adapter inversion.
69
+ *
70
+ * `rendering` is the scene-level render scope (`env.rendering`); it seeds the
71
+ * reload-only `antialias` option at construction and the live shadow/resolution
72
+ * settings (via {@link applyRendererSettings}). Absent → registry defaults.
73
+ *
74
+ * `opts` is additive (T6.1 slice 1, COMPOSITION-DESIGN.md D5 §1/§4): omitted
75
+ * entirely, construction is byte-identical to before (no `alpha`/
76
+ * `preserveDrawingBuffer` keys at all) — the legacy single-canvas host never
77
+ * passes it. The worlds path passes `alpha:true` for every stacked canvas
78
+ * above the bottom one (so its clear-alpha-0 shows the layer below through
79
+ * it) and `preserveDrawingBuffer:true` for every stacked canvas (the
80
+ * recorded capture-tier cost, paid once here rather than re-derived later).
81
+ */
82
+ export function createHostRenderer(
83
+ canvas: HTMLCanvasElement,
84
+ width = window.innerWidth,
85
+ height = window.innerHeight,
86
+ rendering?: RenderScope,
87
+ opts?: { alpha?: boolean; preserveDrawingBuffer?: boolean },
88
+ ): THREE.WebGLRenderer {
89
+ const settings = resolveRenderSettings(rendering);
90
+ const renderer = new THREE.WebGLRenderer({
91
+ canvas,
92
+ antialias: Boolean(settings['antialias']),
93
+ powerPreference: 'high-performance',
94
+ ...(opts?.alpha ? { alpha: true } : {}),
95
+ ...(opts?.preserveDrawingBuffer ? { preserveDrawingBuffer: true } : {}),
96
+ });
97
+ renderer.setSize(width, height);
98
+ applyRendererSettings(renderer, rendering);
99
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
100
+ renderer.toneMapping = toneMappingModes[DEFAULTS.toneMapping.mode] ?? THREE.ACESFilmicToneMapping;
101
+ renderer.toneMappingExposure = DEFAULTS.toneMapping.exposure;
102
+ return renderer;
103
+ }
104
+
105
+ /**
106
+ * Create the scene, camera, and postprocessing composer that a first-party game
107
+ * renders into, given a host-provided renderer. The GPU-only `composer` is
108
+ * created here; a headless host (no GPU) skips this entirely.
109
+ */
110
+ export function createSceneView(
111
+ renderer: THREE.WebGLRenderer,
112
+ width = window.innerWidth,
113
+ height = window.innerHeight,
114
+ ): { scene: THREE.Scene; camera: THREE.PerspectiveCamera; composer: EffectComposer } {
115
+ const scene = new THREE.Scene();
116
+ scene.background = new THREE.Color(0x1a1a2e);
117
+
118
+ const camera = new THREE.PerspectiveCamera(
119
+ DEFAULTS.camera.fov,
120
+ width / height,
121
+ DEFAULTS.camera.near,
122
+ DEFAULTS.camera.far,
123
+ );
124
+ camera.position.set(0, 5, 10);
125
+ camera.lookAt(0, 0, 0);
126
+
127
+ const composer = new EffectComposer(renderer);
128
+ composer.addPass(new RenderPass(scene, camera));
129
+ composer.addPass(
130
+ new EffectPass(
131
+ camera,
132
+ new BloomEffect({ luminanceThreshold: DEFAULTS.postProcessing.bloom.luminanceThreshold }),
133
+ ),
134
+ );
135
+
136
+ return { scene, camera, composer };
137
+ }
138
+
139
+ /**
140
+ * Creates the Three.js renderer, scene, camera, and postprocessing pipeline.
141
+ *
142
+ * This is direct Three.js setup — no abstraction. Edit freely. (Now a thin
143
+ * combination of {@link createHostRenderer} + {@link createSceneView}.)
144
+ */
145
+ export function setupRenderer(
146
+ canvas: HTMLCanvasElement,
147
+ width = window.innerWidth,
148
+ height = window.innerHeight,
149
+ ): RendererContext {
150
+ const renderer = createHostRenderer(canvas, width, height);
151
+ const { scene, camera, composer } = createSceneView(renderer, width, height);
152
+ return { renderer, scene, camera, composer };
153
+ }
154
+
155
+ const toneMappingModes: Record<string, THREE.ToneMapping> = {
156
+ none: THREE.NoToneMapping,
157
+ linear: THREE.LinearToneMapping,
158
+ reinhard: THREE.ReinhardToneMapping,
159
+ cineon: THREE.CineonToneMapping,
160
+ aces: THREE.ACESFilmicToneMapping,
161
+ agx: THREE.AgXToneMapping,
162
+ };
163
+
164
+ const smaaPresets: Record<string, SMAAPreset> = {
165
+ low: SMAAPreset.LOW,
166
+ medium: SMAAPreset.MEDIUM,
167
+ high: SMAAPreset.HIGH,
168
+ ultra: SMAAPreset.ULTRA,
169
+ };
170
+
171
+ /**
172
+ * Rebuild composer passes from scene environment settings.
173
+ * Scenes without toneMapping default to ACES (the runtime default).
174
+ * Code can further modify the composer after this call.
175
+ *
176
+ * Pass objectMap to enable entity-referencing effects (godRays, outline).
177
+ * Pass entities to resolve tag-based references (outline).
178
+ */
179
+ export function applyScenePostProcessing(
180
+ composer: EffectComposer,
181
+ renderer: THREE.WebGLRenderer,
182
+ scene: THREE.Scene,
183
+ camera: THREE.Camera,
184
+ env?: SceneEnvironment,
185
+ objectMap?: Map<string, THREE.Object3D>,
186
+ entityTags?: Map<string, string[]>,
187
+ ): void {
188
+ // Dispose the existing passes before discarding them — removeAllPasses() just
189
+ // drops the references, so their GPU render targets would otherwise leak on
190
+ // every env edit / scene switch.
191
+ for (const pass of composer.passes) {
192
+ (pass as { dispose?: () => void }).dispose?.();
193
+ }
194
+ composer.removeAllPasses();
195
+ composer.addPass(new RenderPass(scene, camera));
196
+
197
+ // ── Tone mapping ──────────────────────────────────────────────────────
198
+ const tm = env?.toneMapping;
199
+ renderer.toneMapping =
200
+ toneMappingModes[tm?.mode ?? DEFAULTS.toneMapping.mode] ?? THREE.ACESFilmicToneMapping;
201
+ renderer.toneMappingExposure = tm?.exposure ?? DEFAULTS.toneMapping.exposure;
202
+
203
+ // ── Master switch ─────────────────────────────────────────────────────
204
+ // When post-processing is disabled at the scene/engine level, render the
205
+ // plain scene (the RenderPass added above) and skip every effect pass. The
206
+ // per-effect `enabled` flags below still apply when the master switch is on.
207
+ if (!resolveRenderSettings(env?.rendering as RenderScope | undefined)['postProcessing']) return;
208
+
209
+ const pp = env?.postProcessing;
210
+ const d = DEFAULTS.postProcessing;
211
+
212
+ // ── Prerequisite passes ───────────────────────────────────────────────
213
+
214
+ // NormalPass — needed by SSAO
215
+ let normalPass: NormalPass | undefined;
216
+ if (pp?.ssao?.enabled) {
217
+ normalPass = new NormalPass(scene, camera);
218
+ composer.addPass(normalPass);
219
+ }
220
+
221
+ // VelocityDepthNormalPass — needed by SSR, SSGI, MotionBlur
222
+ let velocityPass: VelocityDepthNormalPass | undefined;
223
+ if (pp?.ssr?.enabled || pp?.ssgi?.enabled || pp?.motionBlur?.enabled) {
224
+ velocityPass = new VelocityDepthNormalPass(scene, camera);
225
+ composer.addPass(velocityPass);
226
+ }
227
+
228
+ // N8AOPostPass — separate Pass, not an Effect
229
+ if (pp?.n8ao?.enabled) {
230
+ const n8aoPass = new N8AOPostPass(scene, camera as THREE.Camera);
231
+ const cfg = n8aoPass.configuration;
232
+ cfg.aoSamples = pp.n8ao.aoSamples ?? d.n8ao.aoSamples;
233
+ cfg.aoRadius = pp.n8ao.aoRadius ?? d.n8ao.aoRadius;
234
+ cfg.intensity = pp.n8ao.intensity ?? d.n8ao.intensity;
235
+ cfg.denoiseSamples = pp.n8ao.denoiseSamples ?? d.n8ao.denoiseSamples;
236
+ cfg.denoiseRadius = pp.n8ao.denoiseRadius ?? d.n8ao.denoiseRadius;
237
+ cfg.distanceFalloff = pp.n8ao.distanceFalloff ?? d.n8ao.distanceFalloff;
238
+ if (pp.n8ao.screenSpaceRadius !== undefined) cfg.screenSpaceRadius = pp.n8ao.screenSpaceRadius;
239
+ if (pp.n8ao.halfRes !== undefined) cfg.halfRes = pp.n8ao.halfRes;
240
+ if (pp.n8ao.color) cfg.color = new THREE.Color(pp.n8ao.color);
241
+ composer.addPass(n8aoPass);
242
+ }
243
+
244
+ // ── Collect Effect instances ──────────────────────────────────────────
245
+ const effects: Effect[] = [];
246
+
247
+ // Anti-aliasing
248
+ if (pp?.smaa?.enabled) {
249
+ const preset = smaaPresets[pp.smaa.preset ?? d.smaa.preset] ?? SMAAPreset.HIGH;
250
+ effects.push(new SMAAEffect({ preset }));
251
+ }
252
+
253
+ // Ambient occlusion
254
+ if (pp?.ssao?.enabled && normalPass) {
255
+ effects.push(
256
+ new SSAOEffect(camera, normalPass.texture, {
257
+ radius: pp.ssao.radius ?? d.ssao.radius,
258
+ intensity: pp.ssao.intensity ?? d.ssao.intensity,
259
+ bias: pp.ssao.bias ?? d.ssao.bias,
260
+ samples: pp.ssao.samples ?? d.ssao.samples,
261
+ rings: pp.ssao.rings ?? d.ssao.rings,
262
+ fade: pp.ssao.fade ?? d.ssao.fade,
263
+ ...(pp.ssao.color ? { color: new THREE.Color(pp.ssao.color) } : {}),
264
+ }),
265
+ );
266
+ }
267
+
268
+ // SSR (realism-effects)
269
+ if (pp?.ssr?.enabled && velocityPass) {
270
+ effects.push(
271
+ new SSREffect(scene, camera, velocityPass, {
272
+ intensity: pp.ssr.intensity ?? d.ssr.intensity,
273
+ exponent: pp.ssr.exponent ?? d.ssr.exponent,
274
+ distance: pp.ssr.distance ?? d.ssr.distance,
275
+ thickness: pp.ssr.thickness ?? d.ssr.thickness,
276
+ maxRoughness: pp.ssr.maxRoughness ?? d.ssr.maxRoughness,
277
+ }),
278
+ );
279
+ }
280
+
281
+ // SSGI (realism-effects)
282
+ if (pp?.ssgi?.enabled && velocityPass) {
283
+ effects.push(
284
+ new SSGIEffect(scene, camera, velocityPass, {
285
+ intensity: pp.ssgi.intensity ?? d.ssgi.intensity,
286
+ distance: pp.ssgi.distance ?? d.ssgi.distance,
287
+ thickness: pp.ssgi.thickness ?? d.ssgi.thickness,
288
+ maxRoughness: pp.ssgi.maxRoughness ?? d.ssgi.maxRoughness,
289
+ }),
290
+ );
291
+ }
292
+
293
+ // Motion blur (realism-effects)
294
+ if (pp?.motionBlur?.enabled && velocityPass) {
295
+ effects.push(
296
+ new MotionBlurEffect(velocityPass, {
297
+ intensity: pp.motionBlur.intensity ?? d.motionBlur.intensity,
298
+ jitter: pp.motionBlur.jitter ?? d.motionBlur.jitter,
299
+ samples: pp.motionBlur.samples ?? d.motionBlur.samples,
300
+ }),
301
+ );
302
+ }
303
+
304
+ // Bloom
305
+ if (pp?.bloom?.enabled) {
306
+ effects.push(
307
+ new BloomEffect({
308
+ intensity: pp.bloom.intensity ?? d.bloom.intensity,
309
+ luminanceThreshold: pp.bloom.luminanceThreshold ?? d.bloom.luminanceThreshold,
310
+ luminanceSmoothing: pp.bloom.luminanceSmoothing ?? d.bloom.luminanceSmoothing,
311
+ }),
312
+ );
313
+ }
314
+
315
+ // Depth of field
316
+ if (pp?.depthOfField?.enabled) {
317
+ effects.push(
318
+ new DepthOfFieldEffect(camera, {
319
+ worldFocusDistance: pp.depthOfField.worldFocusDistance ?? d.depthOfField.worldFocusDistance,
320
+ worldFocusRange: pp.depthOfField.worldFocusRange ?? d.depthOfField.worldFocusRange,
321
+ bokehScale: pp.depthOfField.bokehScale ?? d.depthOfField.bokehScale,
322
+ focalLength: pp.depthOfField.focalLength ?? d.depthOfField.focalLength,
323
+ }),
324
+ );
325
+ }
326
+
327
+ // Tilt shift
328
+ if (pp?.tiltShift?.enabled) {
329
+ effects.push(
330
+ new TiltShiftEffect({
331
+ offset: pp.tiltShift.offset ?? d.tiltShift.offset,
332
+ rotation: pp.tiltShift.rotation ?? d.tiltShift.rotation,
333
+ focusArea: pp.tiltShift.focusArea ?? d.tiltShift.focusArea,
334
+ feather: pp.tiltShift.feather ?? d.tiltShift.feather,
335
+ }),
336
+ );
337
+ }
338
+
339
+ // God rays (needs entity reference)
340
+ if (pp?.godRays?.enabled && pp.godRays.sourceEntity && objectMap) {
341
+ const lightObj = objectMap.get(pp.godRays.sourceEntity);
342
+ if (lightObj instanceof THREE.Mesh || lightObj instanceof THREE.Points) {
343
+ effects.push(
344
+ new GodRaysEffect(camera, lightObj, {
345
+ density: pp.godRays.density ?? d.godRays.density,
346
+ decay: pp.godRays.decay ?? d.godRays.decay,
347
+ weight: pp.godRays.weight ?? d.godRays.weight,
348
+ exposure: pp.godRays.exposure ?? d.godRays.exposure,
349
+ samples: pp.godRays.samples ?? d.godRays.samples,
350
+ }),
351
+ );
352
+ }
353
+ }
354
+
355
+ // Outline (needs entity tag references)
356
+ if (pp?.outline?.enabled && objectMap) {
357
+ const outlineEffect = new OutlineEffect(scene, camera, {
358
+ edgeStrength: pp.outline.edgeStrength ?? d.outline.edgeStrength,
359
+ pulseSpeed: pp.outline.pulseSpeed ?? d.outline.pulseSpeed,
360
+ visibleEdgeColor: pp.outline.visibleEdgeColor
361
+ ? new THREE.Color(pp.outline.visibleEdgeColor).getHex()
362
+ : new THREE.Color(d.outline.visibleEdgeColor).getHex(),
363
+ hiddenEdgeColor: pp.outline.hiddenEdgeColor
364
+ ? new THREE.Color(pp.outline.hiddenEdgeColor).getHex()
365
+ : new THREE.Color(d.outline.hiddenEdgeColor).getHex(),
366
+ xRay: pp.outline.xRay ?? false,
367
+ });
368
+
369
+ // Add entities matching tags to selection
370
+ const tags = pp.outline.tags ?? [];
371
+ if (tags.length > 0 && entityTags) {
372
+ for (const [entityId, eTags] of entityTags) {
373
+ if (tags.some((t) => eTags.includes(t))) {
374
+ const obj = objectMap.get(entityId);
375
+ if (obj) outlineEffect.selection.add(obj);
376
+ }
377
+ }
378
+ }
379
+
380
+ effects.push(outlineEffect);
381
+ }
382
+
383
+ // Chromatic aberration
384
+ if (pp?.chromaticAberration?.enabled) {
385
+ effects.push(
386
+ new ChromaticAberrationEffect({
387
+ offset: new THREE.Vector2(
388
+ pp.chromaticAberration.offsetX ?? d.chromaticAberration.offsetX,
389
+ pp.chromaticAberration.offsetY ?? d.chromaticAberration.offsetY,
390
+ ),
391
+ radialModulation: pp.chromaticAberration.radialModulation ?? false,
392
+ modulationOffset:
393
+ pp.chromaticAberration.modulationOffset ?? d.chromaticAberration.modulationOffset,
394
+ }),
395
+ );
396
+ }
397
+
398
+ // Lens distortion
399
+ if (pp?.lensDistortion?.enabled) {
400
+ effects.push(
401
+ new LensDistortionEffect({
402
+ distortion: new THREE.Vector2(
403
+ pp.lensDistortion.distortionX ?? d.lensDistortion.distortionX,
404
+ pp.lensDistortion.distortionY ?? d.lensDistortion.distortionY,
405
+ ),
406
+ principalPoint: new THREE.Vector2(0, 0),
407
+ focalLength: new THREE.Vector2(
408
+ pp.lensDistortion.focalLengthX ?? d.lensDistortion.focalLengthX,
409
+ pp.lensDistortion.focalLengthY ?? d.lensDistortion.focalLengthY,
410
+ ),
411
+ skew: pp.lensDistortion.skew ?? d.lensDistortion.skew,
412
+ }),
413
+ );
414
+ }
415
+
416
+ // Shock wave
417
+ if (pp?.shockWave?.enabled) {
418
+ effects.push(
419
+ new ShockWaveEffect(
420
+ camera,
421
+ new THREE.Vector3(
422
+ pp.shockWave.positionX ?? 0,
423
+ pp.shockWave.positionY ?? 0,
424
+ pp.shockWave.positionZ ?? 0,
425
+ ),
426
+ {
427
+ speed: pp.shockWave.speed ?? d.shockWave.speed,
428
+ maxRadius: pp.shockWave.maxRadius ?? d.shockWave.maxRadius,
429
+ waveSize: pp.shockWave.waveSize ?? d.shockWave.waveSize,
430
+ amplitude: pp.shockWave.amplitude ?? d.shockWave.amplitude,
431
+ },
432
+ ),
433
+ );
434
+ }
435
+
436
+ // Color adjustments
437
+ if (pp?.brightnessContrast?.enabled) {
438
+ effects.push(
439
+ new BrightnessContrastEffect({
440
+ brightness: pp.brightnessContrast.brightness ?? d.brightnessContrast.brightness,
441
+ contrast: pp.brightnessContrast.contrast ?? d.brightnessContrast.contrast,
442
+ }),
443
+ );
444
+ }
445
+
446
+ if (pp?.hueSaturation?.enabled) {
447
+ effects.push(
448
+ new HueSaturationEffect({
449
+ hue: pp.hueSaturation.hue ?? d.hueSaturation.hue,
450
+ saturation: pp.hueSaturation.saturation ?? d.hueSaturation.saturation,
451
+ }),
452
+ );
453
+ }
454
+
455
+ if (pp?.sepia?.enabled) {
456
+ effects.push(new SepiaEffect({ intensity: pp.sepia.intensity ?? d.sepia.intensity }));
457
+ }
458
+
459
+ if (pp?.colorDepth?.enabled) {
460
+ effects.push(new ColorDepthEffect({ bits: pp.colorDepth.bits ?? d.colorDepth.bits }));
461
+ }
462
+
463
+ // Film / retro
464
+ if (pp?.noise?.enabled) {
465
+ effects.push(new NoiseEffect({ premultiply: pp.noise.premultiply ?? d.noise.premultiply }));
466
+ }
467
+
468
+ if (pp?.scanline?.enabled) {
469
+ effects.push(new ScanlineEffect({ density: pp.scanline.density ?? d.scanline.density }));
470
+ }
471
+
472
+ if (pp?.dotScreen?.enabled) {
473
+ effects.push(
474
+ new DotScreenEffect({
475
+ angle: pp.dotScreen.angle ?? d.dotScreen.angle,
476
+ scale: pp.dotScreen.scale ?? d.dotScreen.scale,
477
+ }),
478
+ );
479
+ }
480
+
481
+ if (pp?.grid?.enabled) {
482
+ effects.push(
483
+ new GridEffect({
484
+ scale: pp.grid.scale ?? d.grid.scale,
485
+ lineWidth: pp.grid.lineWidth ?? d.grid.lineWidth,
486
+ }),
487
+ );
488
+ }
489
+
490
+ if (pp?.pixelation?.enabled) {
491
+ effects.push(new PixelationEffect(pp.pixelation.granularity ?? d.pixelation.granularity));
492
+ }
493
+
494
+ if (pp?.glitch?.enabled) {
495
+ effects.push(
496
+ new GlitchEffect({
497
+ delay: new THREE.Vector2(
498
+ pp.glitch.delayX ?? d.glitch.delayX,
499
+ pp.glitch.delayY ?? d.glitch.delayY,
500
+ ),
501
+ duration: new THREE.Vector2(
502
+ pp.glitch.durationX ?? d.glitch.durationX,
503
+ pp.glitch.durationY ?? d.glitch.durationY,
504
+ ),
505
+ strength: new THREE.Vector2(
506
+ pp.glitch.strengthX ?? d.glitch.strengthX,
507
+ pp.glitch.strengthY ?? d.glitch.strengthY,
508
+ ),
509
+ columns: pp.glitch.columns ?? d.glitch.columns,
510
+ ratio: pp.glitch.ratio ?? d.glitch.ratio,
511
+ }),
512
+ );
513
+ }
514
+
515
+ // Vignette (last color effect)
516
+ if (pp?.vignette?.enabled) {
517
+ effects.push(
518
+ new VignetteEffect({
519
+ darkness: pp.vignette.darkness ?? d.vignette.darkness,
520
+ offset: pp.vignette.offset ?? d.vignette.offset,
521
+ }),
522
+ );
523
+ }
524
+
525
+ // ── Merge all effects into a single EffectPass ────────────────────────
526
+ if (effects.length > 0) {
527
+ composer.addPass(new EffectPass(camera, ...effects));
528
+ }
529
+ }
@@ -0,0 +1,37 @@
1
+ declare module 'n8ao' {
2
+ import type { Pass } from 'postprocessing';
3
+ import type { Camera, Color, Scene, Texture, WebGLRenderer, WebGLRenderTarget } from 'three';
4
+
5
+ interface N8AOConfiguration {
6
+ aoSamples: number;
7
+ aoRadius: number;
8
+ denoiseSamples: number;
9
+ denoiseRadius: number;
10
+ distanceFalloff: number;
11
+ intensity: number;
12
+ denoiseIterations: number;
13
+ renderMode: 0 | 1 | 2 | 3 | 4;
14
+ color: Color;
15
+ gammaCorrection: boolean;
16
+ screenSpaceRadius: boolean;
17
+ halfRes: boolean;
18
+ depthAwareUpsampling: boolean;
19
+ colorMultiply: boolean;
20
+ transparencyAware: boolean;
21
+ accumulate: boolean;
22
+ }
23
+
24
+ export class N8AOPostPass extends Pass {
25
+ constructor(scene: Scene, camera: Camera, width?: number, height?: number);
26
+ configuration: N8AOConfiguration;
27
+ setDepthTexture(depthTexture: Texture): void;
28
+ setDisplayMode(mode: 'Combined' | 'AO' | 'No AO' | 'Split' | 'Split AO'): void;
29
+ setQualityMode(mode: 'Performance' | 'Low' | 'Medium' | 'High' | 'Ultra'): void;
30
+ setSize(width: number, height: number): void;
31
+ render(
32
+ renderer: WebGLRenderer,
33
+ inputBuffer: WebGLRenderTarget,
34
+ outputBuffer: WebGLRenderTarget,
35
+ ): void;
36
+ }
37
+ }
@@ -0,0 +1,61 @@
1
+ declare module 'realism-effects' {
2
+ import type { Effect, EffectComposer, Pass } from 'postprocessing';
3
+ import type { Camera, Scene } from 'three';
4
+
5
+ export class VelocityDepthNormalPass extends Pass {
6
+ constructor(scene: Scene, camera: Camera);
7
+ }
8
+
9
+ export class VelocityPass extends Pass {
10
+ constructor(scene: Scene, camera: Camera);
11
+ }
12
+
13
+ interface SSGIOptions {
14
+ intensity?: number;
15
+ exponent?: number;
16
+ distance?: number;
17
+ thickness?: number;
18
+ maxRoughness?: number;
19
+ specularOnly?: boolean;
20
+ diffuseOnly?: boolean;
21
+ }
22
+
23
+ export class SSGIEffect extends Effect {
24
+ constructor(
25
+ scene: Scene,
26
+ camera: Camera,
27
+ velocityDepthNormalPass: VelocityDepthNormalPass,
28
+ options?: SSGIOptions,
29
+ );
30
+ }
31
+
32
+ export class SSREffect extends SSGIEffect {
33
+ constructor(
34
+ scene: Scene,
35
+ camera: Camera,
36
+ velocityDepthNormalPass: VelocityDepthNormalPass,
37
+ options?: SSGIOptions,
38
+ );
39
+ }
40
+
41
+ interface MotionBlurOptions {
42
+ intensity?: number;
43
+ jitter?: number;
44
+ samples?: number;
45
+ }
46
+
47
+ export class MotionBlurEffect extends Effect {
48
+ constructor(velocityPass: VelocityDepthNormalPass, options?: MotionBlurOptions);
49
+ }
50
+
51
+ interface AOOptions {
52
+ spp?: number;
53
+ distance?: number;
54
+ distancePower?: number;
55
+ power?: number;
56
+ }
57
+
58
+ export class HBAOEffect extends Effect {
59
+ constructor(composer: EffectComposer, camera: Camera, scene: Scene, options?: AOOptions);
60
+ }
61
+ }