@needle-tools/engine 4.6.0 → 4.6.1-next.cd98b8c

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 (78) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/generateMeshBVH.worker-BaNp_Xtp.js +25 -0
  3. package/dist/{gltf-progressive-Bm9eEfgu.min.js → gltf-progressive-Bl4okF1b.min.js} +1 -1
  4. package/dist/{gltf-progressive-GjIqwSG3.js → gltf-progressive-DSpdn0QT.js} +2 -2
  5. package/dist/{gltf-progressive-Dn6o99rH.umd.cjs → gltf-progressive-P8b8a0qY.umd.cjs} +1 -1
  6. package/dist/{needle-engine.bundle-DsrPZ9gj.js → needle-engine.bundle-BOiTKp9l.js} +8027 -7961
  7. package/dist/{needle-engine.bundle-CoJqbtmp.umd.cjs → needle-engine.bundle-ClaRVK0x.umd.cjs} +152 -148
  8. package/dist/needle-engine.bundle-Dq1fvtLi.min.js +1563 -0
  9. package/dist/needle-engine.js +343 -342
  10. package/dist/needle-engine.min.js +1 -1
  11. package/dist/needle-engine.umd.cjs +1 -1
  12. package/dist/{postprocessing-CRQa6Qxn.umd.cjs → postprocessing-CjW23fio.umd.cjs} +18 -18
  13. package/dist/{postprocessing-D6W1EyZ-.js → postprocessing-DYLNOL3W.js} +4 -3
  14. package/dist/{postprocessing-DF8AlRgW.min.js → postprocessing-xYQWCHFu.min.js} +26 -26
  15. package/dist/{three-DMrv-4ar.umd.cjs → three-B_hneGZr.umd.cjs} +4 -4
  16. package/dist/{three-Bz6X1mrw.js → three-DrqIzZTH.js} +4198 -4198
  17. package/dist/{three-Boa-jOq-.min.js → three-DuDKwKB8.min.js} +33 -33
  18. package/dist/{three-examples-GggCDHv0.js → three-examples-B50TT3Iu.js} +5 -5
  19. package/dist/{three-examples-DuVhxqft.min.js → three-examples-DaDLBuy6.min.js} +14 -14
  20. package/dist/{three-examples-C7ryg8vN.umd.cjs → three-examples-X3OadjXB.umd.cjs} +3 -3
  21. package/dist/{three-mesh-ui-CY6Izc7C.min.js → three-mesh-ui-B3p3gyUz.min.js} +1 -1
  22. package/dist/{three-mesh-ui-CwlN0FUC.umd.cjs → three-mesh-ui-CQiIQIlA.umd.cjs} +1 -1
  23. package/dist/{three-mesh-ui-CLNOfsWn.js → three-mesh-ui-CxuWt7m-.js} +1 -1
  24. package/dist/{vendor-zxXa3Dmr.min.js → vendor-BlSxe9JJ.min.js} +3 -3
  25. package/dist/{vendor-BSD1RQIh.js → vendor-BmYIgaS1.js} +3 -3
  26. package/dist/{vendor-DHr4aqIZ.umd.cjs → vendor-Cavtu3CP.umd.cjs} +3 -3
  27. package/lib/engine/engine_assetdatabase.js +3 -1
  28. package/lib/engine/engine_assetdatabase.js.map +1 -1
  29. package/lib/engine/engine_context.d.ts +2 -2
  30. package/lib/engine/engine_context.js +11 -10
  31. package/lib/engine/engine_context.js.map +1 -1
  32. package/lib/engine/engine_utils_screenshot.d.ts +1 -1
  33. package/lib/engine/engine_utils_screenshot.js +11 -2
  34. package/lib/engine/engine_utils_screenshot.js.map +1 -1
  35. package/lib/engine-components/ReflectionProbe.d.ts +2 -1
  36. package/lib/engine-components/ReflectionProbe.js +4 -1
  37. package/lib/engine-components/ReflectionProbe.js.map +1 -1
  38. package/lib/engine-components/Renderer.js +9 -5
  39. package/lib/engine-components/Renderer.js.map +1 -1
  40. package/lib/engine-components/postprocessing/Effects/Antialiasing.js +2 -1
  41. package/lib/engine-components/postprocessing/Effects/Antialiasing.js.map +1 -1
  42. package/lib/engine-components/postprocessing/Effects/BloomEffect.d.ts +1 -1
  43. package/lib/engine-components/postprocessing/Effects/BloomEffect.js +3 -0
  44. package/lib/engine-components/postprocessing/Effects/BloomEffect.js.map +1 -1
  45. package/lib/engine-components/postprocessing/Effects/Tonemapping.js +10 -6
  46. package/lib/engine-components/postprocessing/Effects/Tonemapping.js.map +1 -1
  47. package/lib/engine-components/postprocessing/PostProcessingEffect.d.ts +18 -0
  48. package/lib/engine-components/postprocessing/PostProcessingEffect.js +20 -1
  49. package/lib/engine-components/postprocessing/PostProcessingEffect.js.map +1 -1
  50. package/lib/engine-components/postprocessing/PostProcessingHandler.d.ts +11 -3
  51. package/lib/engine-components/postprocessing/PostProcessingHandler.js +122 -118
  52. package/lib/engine-components/postprocessing/PostProcessingHandler.js.map +1 -1
  53. package/lib/engine-components/postprocessing/Volume.d.ts +2 -1
  54. package/lib/engine-components/postprocessing/Volume.js +16 -6
  55. package/lib/engine-components/postprocessing/Volume.js.map +1 -1
  56. package/lib/engine-components/postprocessing/index.d.ts +1 -0
  57. package/lib/engine-components/postprocessing/index.js +1 -0
  58. package/lib/engine-components/postprocessing/index.js.map +1 -1
  59. package/lib/engine-components/postprocessing/utils.d.ts +38 -0
  60. package/lib/engine-components/postprocessing/utils.js +98 -0
  61. package/lib/engine-components/postprocessing/utils.js.map +1 -1
  62. package/package.json +2 -2
  63. package/plugins/vite/dependency-watcher.js +8 -2
  64. package/src/engine/engine_assetdatabase.ts +3 -1
  65. package/src/engine/engine_context.ts +14 -11
  66. package/src/engine/engine_utils_screenshot.ts +13 -3
  67. package/src/engine-components/ReflectionProbe.ts +5 -1
  68. package/src/engine-components/Renderer.ts +10 -7
  69. package/src/engine-components/postprocessing/Effects/Antialiasing.ts +2 -1
  70. package/src/engine-components/postprocessing/Effects/BloomEffect.ts +4 -2
  71. package/src/engine-components/postprocessing/Effects/Tonemapping.ts +9 -7
  72. package/src/engine-components/postprocessing/PostProcessingEffect.ts +21 -1
  73. package/src/engine-components/postprocessing/PostProcessingHandler.ts +142 -124
  74. package/src/engine-components/postprocessing/Volume.ts +18 -9
  75. package/src/engine-components/postprocessing/index.ts +1 -0
  76. package/src/engine-components/postprocessing/utils.ts +118 -2
  77. package/dist/generateMeshBVH.worker-Cdfpaq5W.js +0 -25
  78. package/dist/needle-engine.bundle-BDO_N7gN.min.js +0 -1559
@@ -1,5 +1,5 @@
1
1
  import type { Effect, EffectComposer, Pass, ToneMappingEffect as _TonemappingEffect } from "postprocessing";
2
- import { HalfFloatType, NoToneMapping } from "three";
2
+ import { Camera as Camera3, HalfFloatType, NoToneMapping, Scene } from "three";
3
3
 
4
4
  import { showBalloonWarning } from "../../engine/debug/index.js";
5
5
  // import { internal_SetSharpeningEffectModule } from "./Effects/Sharpening.js";
@@ -9,13 +9,13 @@ import type { Constructor } from "../../engine/engine_types.js";
9
9
  import { DeviceUtilities, getParam } from "../../engine/engine_utils.js";
10
10
  import { Camera } from "../Camera.js";
11
11
  import { PostProcessingEffect, PostProcessingEffectContext } from "./PostProcessingEffect.js";
12
+ import { orderEffects, PostprocessingEffectData } from "./utils.js";
12
13
 
13
14
  declare const NEEDLE_USE_POSTPROCESSING: boolean;
14
15
  globalThis["NEEDLE_USE_POSTPROCESSING"] = globalThis["NEEDLE_USE_POSTPROCESSING"] !== undefined ? globalThis["NEEDLE_USE_POSTPROCESSING"] : true;
15
16
 
16
17
 
17
18
  const debug = getParam("debugpost");
18
- const dontMergePasses = getParam("debugpostpasses");
19
19
 
20
20
  const activeKey = Symbol("needle:postprocessing-handler");
21
21
  const autoclearSetting = Symbol("needle:previous-autoclear-state");
@@ -27,7 +27,7 @@ export class PostProcessingHandler {
27
27
 
28
28
  private _composer: EffectComposer | null = null;
29
29
  private _lastVolumeComponents?: PostProcessingEffect[];
30
- private _effects: Array<Effect | Pass> = [];
30
+ private readonly _effects: Array<PostprocessingEffectData> = [];
31
31
 
32
32
  get isActive() {
33
33
  return this._isActive;
@@ -44,7 +44,7 @@ export class PostProcessingHandler {
44
44
  this.context = context;
45
45
  }
46
46
 
47
- apply(components: PostProcessingEffect[]) : Promise<void> {
47
+ apply(components: PostProcessingEffect[]): Promise<void> {
48
48
  if ("env" in import.meta && import.meta.env.VITE_NEEDLE_USE_POSTPROCESSING === "false") {
49
49
  if (debug) console.warn("Postprocessing is disabled via vite env setting");
50
50
  else console.debug("Postprocessing is disabled via vite env setting");
@@ -60,7 +60,7 @@ export class PostProcessingHandler {
60
60
  return this.onApply(this.context, components);
61
61
  }
62
62
 
63
- unapply() {
63
+ unapply(dispose: boolean = true) {
64
64
  if (debug) console.log("Unapplying postprocessing effects");
65
65
  this._isActive = false;
66
66
  if (this._lastVolumeComponents) {
@@ -73,21 +73,26 @@ export class PostProcessingHandler {
73
73
  const active = context[activeKey] as PostProcessingHandler | null;
74
74
  if (active === this) {
75
75
  delete context[activeKey];
76
+
77
+ // Restore the auto clear setting
78
+ if (typeof context.renderer[autoclearSetting] === "boolean") {
79
+ context.renderer.autoClear = context.renderer[autoclearSetting];
80
+ }
76
81
  }
82
+
83
+ this._composer?.removeAllPasses();
84
+ if (dispose) this._composer?.dispose();
85
+
77
86
  if (context.composer === this._composer) {
78
- context.composer?.dispose();
79
87
  context.composer = null;
80
88
  }
81
- if (typeof context.renderer[autoclearSetting] === "boolean") {
82
- context.renderer.autoClear = context.renderer[autoclearSetting];
83
- }
84
89
  }
85
90
 
86
91
  dispose() {
87
- this.unapply();
92
+ this.unapply(true);
88
93
 
89
94
  for (const effect of this._effects) {
90
- effect.dispose();
95
+ effect.effect.dispose();
91
96
  }
92
97
  this._effects.length = 0;
93
98
  this._composer = null;
@@ -105,6 +110,7 @@ export class PostProcessingHandler {
105
110
  // import("./Effects/Sharpening.effect")
106
111
  ]);
107
112
 
113
+
108
114
  // try {
109
115
  // internal_SetSharpeningEffectModule(modules[2]);
110
116
  // }
@@ -141,10 +147,22 @@ export class PostProcessingHandler {
141
147
  // apply or collect effects
142
148
  const res = component.apply(ctx);
143
149
  if (!res) continue;
150
+
151
+
144
152
  if (Array.isArray(res)) {
145
- this._effects.push(...res);
153
+ for (const effect of res) {
154
+ this._effects.push({
155
+ effect,
156
+ priority: component.priority
157
+ });
158
+ }
159
+ }
160
+ else {
161
+ this._effects.push({
162
+ effect: res,
163
+ priority: component.priority
164
+ });
146
165
  }
147
- else this._effects.push(res);
148
166
  }
149
167
  }
150
168
  else {
@@ -157,20 +175,33 @@ export class PostProcessingHandler {
157
175
  if (this.context.renderer.toneMapping != NoToneMapping) {
158
176
  if (!this._effects.find(e => e instanceof MODULES.POSTPROCESSING.MODULE.ToneMappingEffect)) {
159
177
  const tonemapping = new MODULES.POSTPROCESSING.MODULE.ToneMappingEffect();
160
- this._effects.push(tonemapping);
178
+ this._effects.push({ effect: tonemapping });
161
179
  }
162
180
  }
163
181
 
164
182
  this.applyEffects(context);
165
183
  }
166
184
 
185
+ private _anyPassHasDepth = false;
186
+ private _anyPassHasNormal = false;
187
+ private _hasSmaaEffect = false;
188
+
189
+ get anyPassHasDepth() { return this._anyPassHasDepth; }
190
+ get anyPassHasNormal() { return this._anyPassHasNormal; }
191
+ get hasSmaaEffect() { return this._hasSmaaEffect; }
192
+
167
193
 
168
194
  /** Build composer passes */
169
195
  private applyEffects(context: Context) {
196
+ // Reset state
197
+ this._anyPassHasDepth = false;
198
+ this._anyPassHasNormal = false;
199
+ this._hasSmaaEffect = false;
170
200
 
171
201
  const effectsOrPasses = this._effects;
172
-
173
- if (effectsOrPasses.length <= 0) return;
202
+ if (effectsOrPasses.length <= 0) {
203
+ return;
204
+ }
174
205
 
175
206
  const camera = context.mainCameraComponent as Camera;
176
207
  const renderer = context.renderer;
@@ -180,8 +211,18 @@ export class PostProcessingHandler {
180
211
  // Store the auto clear setting because the postprocessing composer just disables it
181
212
  // and when we disable postprocessing we want to restore the original setting
182
213
  // https://github.com/pmndrs/postprocessing/blob/271944b74b543a5b743a62803a167b60cc6bb4ee/src/core/EffectComposer.js#L230C12-L230C12
214
+ // First we need to get the previously set autoClear setting, if it exists
215
+ if (typeof renderer[autoclearSetting] === "boolean") {
216
+ renderer.autoClear = renderer[autoclearSetting];
217
+ }
183
218
  renderer[autoclearSetting] = renderer.autoClear;
184
219
 
220
+ for (const ef of effectsOrPasses) {
221
+ if (ef instanceof MODULES.POSTPROCESSING.MODULE.SMAAEffect) {
222
+ this._hasSmaaEffect = true;
223
+ }
224
+ }
225
+
185
226
  // create composer and set active on context
186
227
  if (!this._composer) {
187
228
  // const hdrRenderTarget = new WebGLRenderTarget(window.innerWidth, window.innerHeight, { type: HalfFloatType });
@@ -194,11 +235,13 @@ export class PostProcessingHandler {
194
235
  if (context.composer && context.composer !== this._composer) {
195
236
  console.warn("There's already an active EffectComposer in your scene: replacing it with a new one. This might cause unexpected behaviour. Make sure to only use one PostprocessingManager/Volume in your scene.");
196
237
  }
238
+
197
239
  context.composer = this._composer;
198
240
  const composer = context.composer;
199
241
  composer.setMainCamera(cam);
200
242
  composer.setRenderer(renderer);
201
243
  composer.setMainScene(scene);
244
+ composer.autoRenderToScreen = true; // Must be enabled because it might be disabled during addPass by the composer itself (depending on the effect's settings or order)
202
245
 
203
246
  for (const prev of composer.passes)
204
247
  prev.dispose();
@@ -210,64 +253,100 @@ export class PostProcessingHandler {
210
253
  screenpass.mainScene = scene;
211
254
  composer.addPass(screenpass);
212
255
 
213
- const automaticEffectsOrdering = true;
214
- if (automaticEffectsOrdering && !dontMergePasses) {
215
- try {
216
- this.orderEffects();
256
+ try {
257
+ orderEffects(this._effects);
217
258
 
218
- const effects: Array<Effect> = [];
259
+ const effectsToMerge: Array<Effect> = [];
260
+ let hasConvolutionEffectInArray = false;
219
261
 
220
- for (const ef of effectsOrPasses) {
221
- if (ef instanceof MODULES.POSTPROCESSING.MODULE.Effect)
222
- effects.push(ef as Effect);
223
- else if (ef instanceof MODULES.POSTPROCESSING.MODULE.Pass) {
224
- composer.addPass(ef as Pass);
225
- }
226
- else {
227
- // seems some effects are not correctly typed, but three can deal with them,
228
- // so we might need to just pass them through
229
- composer.addPass(ef);
262
+ for (const entry of effectsOrPasses) {
263
+ const ef = entry.effect;
264
+ if (ef instanceof MODULES.POSTPROCESSING.MODULE.Effect) {
265
+ const attributes = ef.getAttributes();
266
+ const convolution = MODULES.POSTPROCESSING.MODULE.EffectAttribute.CONVOLUTION;
267
+ if (attributes & convolution) {
268
+ console.log("[PostProcessing] Convolution effect detected: " + ef.name, hasConvolutionEffectInArray);
269
+ if (hasConvolutionEffectInArray) {
270
+ hasConvolutionEffectInArray = false;
271
+ console.log("[PostProcessing] Merging effects with convolution effect in array", effectsToMerge.map(e => e.name).join(", "));
272
+ this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
273
+
274
+ }
275
+ hasConvolutionEffectInArray = true;
230
276
  }
277
+ // Otherwise we can merge it
278
+ effectsToMerge.push(ef as Effect);
231
279
  }
232
-
233
- // create and apply uber pass
234
- if (effects.length > 0) {
235
- const pass = new MODULES.POSTPROCESSING.MODULE.EffectPass(cam, ...effects);
236
- pass.name = effects.map(e => e.name).join(" ");
237
- pass.mainScene = scene;
238
- pass.enabled = true;
239
- composer.addPass(pass);
280
+ else if (ef instanceof MODULES.POSTPROCESSING.MODULE.Pass) {
281
+ hasConvolutionEffectInArray = false;
282
+ this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
283
+ ef.renderToScreen = false;
284
+ composer.addPass(ef as Pass);
285
+ }
286
+ else {
287
+ // seems some effects are not correctly typed, but three can deal with them,
288
+ // so we might need to just pass them through
289
+ hasConvolutionEffectInArray = false;
290
+ this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
291
+ composer.addPass(ef);
240
292
  }
241
293
  }
242
- catch (e) {
243
- console.error("Error while applying postprocessing effects", e);
244
- composer.removeAllPasses();
245
- }
294
+
295
+ this.createPassForMergeableEffects(effectsToMerge, composer, cam, scene);
296
+ }
297
+ catch (e) {
298
+ console.error("Error while applying postprocessing effects", e);
299
+ composer.removeAllPasses();
246
300
  }
247
- else {
248
- // we still want to sort passes, but we do not want to merge them for debugging
249
- if (automaticEffectsOrdering)
250
- this.orderEffects();
251
301
 
252
- for (const ef of effectsOrPasses) {
253
- if (ef instanceof MODULES.POSTPROCESSING.MODULE.Effect) {
254
- const pass = new MODULES.POSTPROCESSING.MODULE.EffectPass(cam, ef as Effect);
255
- pass.name = ef.name;
256
- composer.addPass(pass);
257
- }
258
- else if (ef instanceof MODULES.POSTPROCESSING.MODULE.Pass)
259
- composer.addPass(ef as Pass);
260
- else
261
- // seems some effects are not correctly typed, but three can deal with them,
262
- // so we just pass them through
263
- composer.addPass(ef);
302
+ // The last pass is the one that renders to the screen, so we need to set the gamma correction for it (and enable it for all others)
303
+ for (let i = 0; i < composer.passes.length; i++) {
304
+ const pass = composer.passes[i];
305
+ const isLast = i === composer.passes.length - 1;
306
+ if ((pass as any)?.configuration !== undefined) {
307
+ (pass as any).configuration.gammaCorrection = isLast;
264
308
  }
309
+ else if ("autosetGamma" in pass) {
310
+ // Some effects have a autosetGamma property that we can use to set the gamma correction
311
+ pass.autosetGamma = isLast;
312
+ }
313
+
314
+ this._anyPassHasDepth ||= pass.needsDepthTexture;
315
+ if (pass instanceof MODULES.POSTPROCESSING.MODULE.NormalPass) {
316
+ this._anyPassHasNormal = true;
317
+ }
318
+ if (pass instanceof MODULES.POSTPROCESSING.MODULE.SMAAEffect) {
319
+ this._hasSmaaEffect = true;
320
+ }
321
+ }
322
+
323
+ // DEBUG LAND BELOW
324
+ if (debug) console.log("[PostProcessing] Passes →", [...composer.passes], "\n---------------------------------\n• " + composer.passes.map(i => i.name).join("\n• ") + "\n");
325
+ if (debug) this._onCreateEffectsDebug(this._composer!, cam);
326
+ }
327
+
328
+
329
+
330
+ /** Should be called before `composer.addPass()` to create an effect pass with all previously collected effects that can be merged up to that point */
331
+ private createPassForMergeableEffects(effects: Array<Effect>, composer: EffectComposer, camera: Camera3, scene: Scene) {
332
+ if (effects.length > 0) {
333
+ const pass = new MODULES.POSTPROCESSING.MODULE.EffectPass(camera, ...effects);
334
+ pass.name = effects.map(e => e.name).join(", ");
335
+ pass.mainScene = scene;
336
+ pass.enabled = true;
337
+ pass.renderToScreen = false;
338
+ composer.addPass(pass);
339
+ effects.length = 0; // Clear effects after adding them to the pass
265
340
  }
341
+ }
266
342
 
267
- if (debug) {
268
343
 
269
- console.log("[PostProcessing] Passes →", composer.passes);
270
344
 
345
+ private _menuEntry: HTMLSelectElement | null = null;
346
+ private _passIndices: number[] | null = null;
347
+
348
+ private _onCreateEffectsDebug(composer: EffectComposer, cam: Camera3) {
349
+ if (debug === "passes") {
271
350
  // DepthEffect for debugging purposes, disabled by default, can be selected in the debug pass select
272
351
  const depthEffect = new MODULES.POSTPROCESSING.MODULE.DepthEffect({
273
352
  blendFunction: MODULES.POSTPROCESSING.MODULE.BlendFunction.NORMAL,
@@ -303,7 +382,7 @@ export class PostProcessingHandler {
303
382
  if (menu && this._passIndices === null) {
304
383
  if (this._menuEntry)
305
384
  this._menuEntry.remove();
306
-
385
+
307
386
  const select = document.createElement("select");
308
387
  select.multiple = true;
309
388
  const defaultOpt = document.createElement("option");
@@ -327,73 +406,12 @@ export class PostProcessingHandler {
327
406
  else {
328
407
  this._passIndices = indices;
329
408
  }
330
-
331
- this.applyEffects(context);
409
+ this.applyEffects(this.context);
332
410
  });
333
411
  }
334
412
  }
335
413
  }
336
414
 
337
- private _menuEntry: HTMLSelectElement | null = null;
338
- private _passIndices: number[] | null = null;
339
-
340
- private orderEffects() {
341
- if (debug) console.log("Before ordering effects", [...this._effects]);
342
-
343
-
344
-
345
- // Order of effects for correct results.
346
- // Aligned with https://github.com/pmndrs/postprocessing/wiki/Effect-Merging#effect-execution-order
347
- // We can not put this into global scope because then the module might not yet be initialized
348
- effectsOrder ??= [
349
- MODULES.POSTPROCESSING.MODULE.NormalPass,
350
- MODULES.POSTPROCESSING.MODULE.DepthDownsamplingPass,
351
- MODULES.POSTPROCESSING.MODULE.SMAAEffect,
352
- MODULES.POSTPROCESSING.MODULE.SSAOEffect,
353
- MODULES.POSTPROCESSING_AO.MODULE.N8AOPostPass,
354
- MODULES.POSTPROCESSING.MODULE.TiltShiftEffect,
355
- MODULES.POSTPROCESSING.MODULE.DepthOfFieldEffect,
356
- MODULES.POSTPROCESSING.MODULE.ChromaticAberrationEffect,
357
- MODULES.POSTPROCESSING.MODULE.BloomEffect,
358
- MODULES.POSTPROCESSING.MODULE.SelectiveBloomEffect,
359
- MODULES.POSTPROCESSING.MODULE.VignetteEffect,
360
- MODULES.POSTPROCESSING.MODULE.PixelationEffect,
361
- MODULES.POSTPROCESSING.MODULE.ToneMappingEffect,
362
- MODULES.POSTPROCESSING.MODULE.HueSaturationEffect,
363
- MODULES.POSTPROCESSING.MODULE.BrightnessContrastEffect,
364
- // __SHARPENING_MODULE._SharpeningEffect,
365
- ];
366
-
367
-
368
- // TODO: enforce correct order of effects (e.g. DOF before Bloom)
369
- const effects = this._effects;
370
- effects.sort((a, b) => {
371
- // we use find index here because sometimes constructor names are prefixed with `_`
372
- // TODO: find a more robust solution that isnt name based (not sure if that exists tho... maybe we must give effect TYPES some priority/index)
373
- const aidx = effectsOrder!.findIndex(e => a.constructor.name.endsWith(e.name));
374
- const bidx = effectsOrder!.findIndex(e => b.constructor.name.endsWith(e.name));
375
- // Unknown effects should be rendered first
376
- if (aidx < 0) {
377
- if (debug) console.warn("Unknown effect found: ", a.constructor.name);
378
- return -1;
379
- }
380
- else if (bidx < 0) {
381
- if (debug) console.warn("Unknown effect found: ", b.constructor.name);
382
- return 1;
383
- }
384
- if (aidx < 0) return 1;
385
- if (bidx < 0) return -1;
386
- return aidx - bidx;
387
- });
388
- if (debug) console.log("After ordering effects", [...this._effects]);
389
- for (let i = 0; i < effects.length; i++) {
390
- const effect = effects[i] as any;
391
- if (effect?.configuration?.gammaCorrection !== undefined) {
392
- const isLast = i === effects.length - 1;
393
- effect.configuration.gammaCorrection = isLast;
394
- }
395
- }
396
- }
397
415
  }
398
416
 
399
417
  let effectsOrder: Array<Constructor<Effect | Pass>> | null = null;
@@ -56,6 +56,9 @@ export class Volume extends Behaviour implements IEditorModificationReceiver, IP
56
56
  return this._activeEffects;
57
57
  }
58
58
 
59
+ get dirty() { return this._isDirty; }
60
+ set dirty(value: boolean) { this._isDirty = value; }
61
+
59
62
  @serializeable(VolumeProfile)
60
63
  sharedProfile?: VolumeProfile;
61
64
 
@@ -185,7 +188,17 @@ export class Volume extends Behaviour implements IEditorModificationReceiver, IP
185
188
  this.context.composer.setMainScene(this.context.scene);
186
189
 
187
190
  const composer = this.context.composer;
188
- if (this.multisampling === "auto") {
191
+
192
+ // If the postprocessing handler is using depth+normals (e.g. with SMAA) we ALWAYS disable multisampling to avoid ugly edges
193
+ if (this._postprocessing && (this._postprocessing.hasSmaaEffect)) {
194
+ if (composer.multisampling !== 0) {
195
+ composer.multisampling = 0;
196
+ if (debug || isDevEnvironment()) {
197
+ console.warn(`[PostProcessing] Disabling multisampling because depth or normals are used. If you need anti-aliasing, consider adding an Antialiasing component or SMAA effect to the PostprocessingManager.`);
198
+ }
199
+ }
200
+ }
201
+ else if (this.multisampling === "auto") {
189
202
 
190
203
  const timeSinceLastChange = this.context.time.realtimeSinceStartup - this._multisampleAutoChangeTime;
191
204
 
@@ -244,7 +257,7 @@ export class Volume extends Behaviour implements IEditorModificationReceiver, IP
244
257
  private _isDirty: boolean = false;
245
258
 
246
259
  private apply() {
247
- if (debug) console.log(`Apply PostProcessing "${this.name}"`);
260
+ if (debug) console.log(`Apply PostProcessing "${this.name || "unnamed"}"`);
248
261
 
249
262
  if (isDevEnvironment()) {
250
263
  if (this._lastApplyTime !== undefined && Date.now() - this._lastApplyTime < 100) {
@@ -256,7 +269,6 @@ export class Volume extends Behaviour implements IEditorModificationReceiver, IP
256
269
  }
257
270
 
258
271
  this._isDirty = false;
259
- this.unapply();
260
272
 
261
273
  this._activeEffects.length = 0;
262
274
  // get from profile
@@ -299,16 +311,13 @@ export class Volume extends Behaviour implements IEditorModificationReceiver, IP
299
311
  console.warn(`[PostProcessing] No composer found`);
300
312
  }
301
313
  })
302
-
314
+ }
315
+ else {
316
+ this._postprocessing?.unapply(false);
303
317
  }
304
318
 
305
319
  }
306
320
 
307
- private unapply() {
308
- this._postprocessing?.unapply();
309
- }
310
-
311
-
312
321
  private _applyPostQueue() {
313
322
  if (this._modificationQueue) {
314
323
  for (const entry of this._modificationQueue.values()) this.onEditorModification(entry);
@@ -3,3 +3,4 @@ export * from "./PostProcessingHandler.js"
3
3
  export { PostProcessingManager } from "./Volume.js"
4
4
  export * from "./VolumeParameter.js"
5
5
  export * from "./VolumeProfile.js";
6
+ export { PostProcessingEffectPriority } from "./utils.js";
@@ -1,9 +1,11 @@
1
+ import type { Effect, Pass } from "postprocessing";
1
2
  import { Object3D } from "three";
2
3
 
3
4
  import { isDevEnvironment } from "../../engine/debug/index.js";
4
5
  import { addComponent } from "../../engine/engine_components.js";
5
6
  import { foreachComponentEnumerator } from "../../engine/engine_gameobject.js";
6
- import { ConstructorConcrete, IComponent } from "../../engine/engine_types.js";
7
+ import { MODULES } from "../../engine/engine_modules.js";
8
+ import { Constructor, ConstructorConcrete, IComponent } from "../../engine/engine_types.js";
7
9
  import { getParam } from "../../engine/engine_utils.js";
8
10
  import { type PostProcessingEffect } from "./PostProcessingEffect.js";
9
11
 
@@ -11,6 +13,8 @@ export const debug = getParam("debugpost");
11
13
 
12
14
  export type IPostProcessingManager = IComponent & {
13
15
  get isPostProcessingManager(): boolean;
16
+ get dirty(): boolean;
17
+ set dirty(value: boolean);
14
18
  addEffect(effect: PostProcessingEffect): void;
15
19
  removeEffect(effect: PostProcessingEffect): void;
16
20
  }
@@ -50,4 +54,116 @@ export function getPostProcessingManager(effect: PostProcessingEffect): IPostPro
50
54
  }
51
55
  }
52
56
  return manager;
53
- }
57
+ }
58
+
59
+
60
+
61
+ export type PostprocessingEffectData = {
62
+ effect: Effect | Pass;
63
+ priority?: number,
64
+ }
65
+
66
+ /**
67
+ * Default priority for post-processing effects. This can be used to sort effects by their rendering order when creating custom effects.
68
+ * E.g. in your custom effect, you can set `priority: PostProcessingEffectPriority.Bloom + 1;` to ensure it gets rendered after the bloom effect.
69
+ * OR `priority: PostProcessingEffectPriority.Bloom - 1;` to ensure it gets rendered before the bloom effect.
70
+ * @example
71
+ * ```typescript
72
+ * import { PostProcessingEffectPriority } from "@needle-tools/engine"
73
+ *
74
+ * export class MyCustomEffect extends PostProcessingEffect {
75
+ * priority: PostProcessingEffectPriority.Bloom + 1; // render after bloom
76
+ *
77
+ * // ... your effect code
78
+ * }
79
+ * ```
80
+ */
81
+ export const PostProcessingEffectPriority = {
82
+ NormalPass: 0,
83
+ DepthDownsamplingPass: 10,
84
+ SMAA: 20,
85
+ SSAO: 30,
86
+ TiltShift: 40,
87
+ DepthOfField: 50,
88
+ ChromaticAberration: 60,
89
+ Bloom: 70,
90
+ Vignette: 80,
91
+ Pixelation: 90,
92
+ ToneMapping: 100,
93
+ HueSaturation: 110,
94
+ BrightnessContrast: 120,
95
+ // Sharpening: 130,
96
+ }
97
+ // let effectsOrder: Array<Constructor<Effect | Pass>> | null = null;
98
+
99
+ let builtinOrder: Map<Constructor<Effect | Pass>, number> | null = null;
100
+
101
+ export function orderEffects(effects: Array<PostprocessingEffectData>) {
102
+ if (debug === "verbose") console.debug("Before ordering effects", [...effects]);
103
+
104
+ if (!builtinOrder) {
105
+ builtinOrder = new Map<Constructor<Effect | Pass>, number>();
106
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.NormalPass, PostProcessingEffectPriority.NormalPass);
107
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.DepthDownsamplingPass, PostProcessingEffectPriority.DepthDownsamplingPass);
108
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.SMAAEffect, PostProcessingEffectPriority.SMAA);
109
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.SSAOEffect, PostProcessingEffectPriority.SSAO);
110
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.TiltShiftEffect, PostProcessingEffectPriority.TiltShift);
111
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.DepthOfFieldEffect, PostProcessingEffectPriority.DepthOfField);
112
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.ChromaticAberrationEffect, PostProcessingEffectPriority.ChromaticAberration);
113
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.BloomEffect, PostProcessingEffectPriority.Bloom);
114
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.VignetteEffect, PostProcessingEffectPriority.Vignette);
115
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.PixelationEffect, PostProcessingEffectPriority.Pixelation);
116
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.ToneMappingEffect, PostProcessingEffectPriority.ToneMapping);
117
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.HueSaturationEffect, PostProcessingEffectPriority.HueSaturation);
118
+ builtinOrder.set(MODULES.POSTPROCESSING.MODULE.BrightnessContrastEffect, PostProcessingEffectPriority.BrightnessContrast);
119
+ }
120
+
121
+ // enforce correct order of effects (e.g. DOF before Bloom)
122
+ effects.sort((a, b) => {
123
+ // we use find index here because sometimes constructor names are prefixed with `_`
124
+ // TODO: find a more robust solution that isnt name based (not sure if that exists tho... maybe we must give effect TYPES some priority/index)
125
+ const aidx = typeof a.priority === "number" ? a.priority : builtinOrder!.get(a.effect.constructor as Constructor<Effect | Pass>) || -1;
126
+ const bidx = typeof b.priority === "number" ? b.priority : builtinOrder!.get(b.effect.constructor as Constructor<Effect | Pass>) || -1;
127
+
128
+ // Unknown effects should be rendered first
129
+ if (aidx < 0) {
130
+ if (debug) console.warn("Unknown effect found: ", a.constructor.name);
131
+ return -1;
132
+ }
133
+ else if (bidx < 0) {
134
+ if (debug) console.warn("Unknown effect found: ", b.constructor.name);
135
+ return 1;
136
+ }
137
+ // if (aidx < 0) return 1;
138
+ // if (bidx < 0) return -1;
139
+ return aidx - bidx;
140
+ });
141
+
142
+ // effects.sort((a, b) => {
143
+ // if (a.beforeEffect) {
144
+ // const beforeA = effectsOrder!.findIndex(e => a.beforeEffect!.constructor.name.endsWith(e.name));
145
+ // if (beforeA >= 0) {
146
+ // return -1; // before effect should be rendered first
147
+ // }
148
+ // else {
149
+ // return 1; // no before effect, so we can keep the order
150
+ // }
151
+ // }
152
+ // else if (b.beforeEffect) {
153
+ // const beforeB = effectsOrder!.findIndex(e => b.beforeEffect!.constructor.name.endsWith(e.name));
154
+ // if (beforeB >= 0) {
155
+ // return 1; // before effect should be rendered first
156
+ // }
157
+ // else if (a.beforeEffect) {
158
+ // return -1; // no before effect, so we can keep the order
159
+ // }
160
+
161
+ // }
162
+ // return 0; // no before effect, so we can keep the order
163
+ // });
164
+
165
+
166
+ if (debug === "verbose") console.debug("After ordering effects", [...effects]);
167
+ }
168
+
169
+