@needle-tools/engine 2.67.3-pre → 2.67.5-pre

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 (24) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/needle-engine.js +2732 -2726
  3. package/dist/needle-engine.umd.cjs +78 -78
  4. package/lib/engine/engine_setup.js +4 -2
  5. package/lib/engine/engine_setup.js.map +1 -1
  6. package/lib/engine-components/postprocessing/Effects/Bloom.js +13 -11
  7. package/lib/engine-components/postprocessing/Effects/Bloom.js.map +1 -1
  8. package/lib/engine-components/postprocessing/Effects/ColorAdjustments.js +20 -35
  9. package/lib/engine-components/postprocessing/Effects/ColorAdjustments.js.map +1 -1
  10. package/lib/engine-components/postprocessing/Effects/DepthOfField.d.ts +0 -2
  11. package/lib/engine-components/postprocessing/Effects/DepthOfField.js +3 -11
  12. package/lib/engine-components/postprocessing/Effects/DepthOfField.js.map +1 -1
  13. package/lib/engine-components/postprocessing/PostProcessingHandler.js +2 -0
  14. package/lib/engine-components/postprocessing/PostProcessingHandler.js.map +1 -1
  15. package/lib/engine-components/postprocessing/Volume.d.ts +2 -1
  16. package/lib/engine-components/postprocessing/Volume.js +31 -14
  17. package/lib/engine-components/postprocessing/Volume.js.map +1 -1
  18. package/package.json +1 -1
  19. package/src/engine/engine_setup.ts +4 -2
  20. package/src/engine-components/postprocessing/Effects/Bloom.ts +13 -11
  21. package/src/engine-components/postprocessing/Effects/ColorAdjustments.ts +18 -36
  22. package/src/engine-components/postprocessing/Effects/DepthOfField.ts +3 -15
  23. package/src/engine-components/postprocessing/PostProcessingHandler.ts +2 -0
  24. package/src/engine-components/postprocessing/Volume.ts +213 -191
@@ -1,4 +1,4 @@
1
- import { BloomEffect, SelectiveBloomEffect } from "postprocessing";
1
+ import { BlendFunction, BloomEffect, SelectiveBloomEffect } from "postprocessing";
2
2
  import { serializable } from "../../../engine/engine_serialization";
3
3
  import { PostProcessingEffect } from "../PostProcessingEffect";
4
4
  import { VolumeParameter } from "../VolumeParameter";
@@ -25,14 +25,14 @@ export class Bloom extends PostProcessingEffect {
25
25
  this.scatter.defaultValue = .2;
26
26
 
27
27
  if (this.selectiveBloom) {
28
- this.threshold.valueProcessor = (v: number) => v * .5;// / (Math.PI * 2);
29
- this.intensity.valueProcessor = (v: number) => v * Math.PI * 2;
30
- this.scatter.valueProcessor = (v: number) => .2 + Math.pow((1 - v), 2);
28
+ this.threshold.valueProcessor = (v: number) => v;
29
+ this.intensity.valueProcessor = (v: number) => v;
30
+ this.scatter.valueProcessor = (v: number) => 1 * Math.PI * (1 - v);
31
31
  }
32
32
  else {
33
- this.threshold.valueProcessor = (v: number) => v / (Math.PI * 2);
33
+ this.threshold.valueProcessor = (v: number) => v;
34
34
  this.intensity.valueProcessor = (v: number) => v;// * 2.2;
35
- this.scatter.valueProcessor = (v: number) => (1 - v);
35
+ this.scatter.valueProcessor = (v: number) => 100 * (1 - v);
36
36
  }
37
37
  }
38
38
 
@@ -41,10 +41,11 @@ export class Bloom extends PostProcessingEffect {
41
41
  if (this.selectiveBloom) {
42
42
  // https://github.com/pmndrs/postprocessing/blob/64d2829f014cfec97a46bf3c109f3abc55af0715/demo/src/demos/BloomDemo.js#L265
43
43
  const selectiveBloom = bloom = new SelectiveBloomEffect(this.context.scene, this.context.mainCamera!, {
44
+ blendFunction: BlendFunction.ADD,
45
+ mipmapBlur: true,
44
46
  luminanceThreshold: this.threshold.value,
45
47
  luminanceSmoothing: this.scatter.value,
46
48
  intensity: this.intensity.value,
47
- mipmapBlur: false,
48
49
  });
49
50
  selectiveBloom.inverted = true;
50
51
  }
@@ -53,10 +54,10 @@ export class Bloom extends PostProcessingEffect {
53
54
  }
54
55
 
55
56
 
56
- const blur = bloom["mipmapBlurPass"];
57
- if (typeof blur === "object") {
58
- blur.radius = this.scatter.value;
59
- }
57
+ // const blur = bloom["mipmapBlurPass"];
58
+ // if (typeof blur === "object") {
59
+ // blur.radius = this.scatter.value;
60
+ // }
60
61
 
61
62
  this.intensity.onValueChanged = newValue => {
62
63
  bloom!.intensity = newValue;
@@ -65,6 +66,7 @@ export class Bloom extends PostProcessingEffect {
65
66
  bloom!.luminanceMaterial.threshold = newValue;
66
67
  };
67
68
  this.scatter.onValueChanged = newValue => {
69
+ bloom!.luminancePass.enabled = true;
68
70
  bloom!.luminanceMaterial.smoothing = newValue;
69
71
  };
70
72
 
@@ -3,7 +3,7 @@ import { serializable } from "../../../engine/engine_serialization";
3
3
  import { EffectProviderResult, PostProcessingEffect } from "../PostProcessingEffect";
4
4
  import { VolumeParameter } from "../VolumeParameter";
5
5
  import { registerCustomEffectType } from "../VolumeProfile";
6
- import { NoToneMapping } from "three";
6
+ import { CustomToneMapping, LinearToneMapping, NoToneMapping } from "three";
7
7
 
8
8
 
9
9
  export class ColorAdjustments extends PostProcessingEffect {
@@ -26,20 +26,16 @@ export class ColorAdjustments extends PostProcessingEffect {
26
26
 
27
27
  init() {
28
28
  this.postExposure!.valueProcessor = v => {
29
- // when using tone mapping, the exposure will just be set to the renderer
30
- if (this.context.renderer.toneMapping !== NoToneMapping && v > 0) {
31
- return Math.pow(2, v);
32
- }
33
- // Custom effects need modification
34
- else {
35
- return v / (2 * Math.PI);
36
- }
29
+ v = Math.pow(2, v);
30
+ return v;
37
31
  }
38
32
  this.postExposure.defaultValue = 0;
39
33
 
40
34
  this.contrast.valueProcessor = (v: number) => {
41
- let divisor = 100;
42
- if (v > 0) divisor *= Math.PI * 2;
35
+ let divisor = 1;
36
+ if (v > 0) divisor = 200;
37
+ else if (v < 0) divisor = 100;
38
+ // if (v > 0) divisor *= Math.PI;
43
39
  const val = v / divisor;
44
40
  return val;
45
41
  };
@@ -49,7 +45,8 @@ export class ColorAdjustments extends PostProcessingEffect {
49
45
  this.hueShift.defaultValue = 0;
50
46
 
51
47
  this.saturation.valueProcessor = (v: number) => {
52
- return (v / 180);
48
+ if (v < 0) return (v / 100);
49
+ return (v / (100 * Math.PI));
53
50
  }
54
51
  this.saturation.defaultValue = 0;
55
52
  }
@@ -60,39 +57,24 @@ export class ColorAdjustments extends PostProcessingEffect {
60
57
 
61
58
 
62
59
  onCreateEffect(): EffectProviderResult {
63
-
60
+ if (this.context.renderer.toneMapping === NoToneMapping && this.postExposure.overrideState)
61
+ this.context.renderer.toneMapping = LinearToneMapping;
64
62
  const brightnesscontrast = new BrightnessContrastEffect();
65
- // TODO: darkening etc doesnt really work well in all cases right now
66
63
  this.postExposure!.onValueChanged = v => {
67
- // We can use the builtin exposure when tonemapping is set
68
- if (this.context.renderer.toneMapping !== NoToneMapping) {
69
- if (v === 0) {
70
- this.context.renderer.toneMappingExposure = 1;
71
- brightnesscontrast.brightness = 0;
72
- }
73
- else if (v > 0) {
74
- this.context.renderer.toneMappingExposure = v;
75
- brightnesscontrast.brightness = 0;
76
- }
77
- else {
78
- this.context.renderer.toneMappingExposure = v;
79
- brightnesscontrast.brightness = 0;
80
- }
81
- }
82
- // Otherwise use the effect (they look sligthly different/behave different unfortunately)
83
- else {
84
- this.context.renderer.toneMappingExposure = 0;
85
- brightnesscontrast.brightness = v;
86
- }
64
+ if (this.context.renderer.toneMapping === NoToneMapping)
65
+ this.context.renderer.toneMapping = LinearToneMapping;
66
+ this.context.renderer.toneMappingExposure = v;
67
+ }
68
+ this.contrast!.onValueChanged = v => {
69
+ brightnesscontrast.contrast = v;
87
70
  }
88
- this.contrast!.onValueChanged = v => brightnesscontrast.contrast = v;
89
71
 
90
72
 
91
73
  const hueSaturationEffect = new HueSaturationEffect();
92
74
  this.hueShift!.onValueChanged = v => hueSaturationEffect.hue = v;
93
75
  this.saturation!.onValueChanged = v => hueSaturationEffect.saturation = v;
94
76
 
95
- return [brightnesscontrast, hueSaturationEffect];
77
+ return [hueSaturationEffect, brightnesscontrast];
96
78
  }
97
79
 
98
80
  // apply() {
@@ -61,7 +61,9 @@ export class DepthOfField extends PostProcessingEffect {
61
61
  bokehScale: 20,
62
62
  });
63
63
 
64
- this.focusDistance.onValueChanged = v => dof.circleOfConfusionMaterial.worldFocusDistance = v;
64
+ this.focusDistance.onValueChanged = v => {
65
+ dof.circleOfConfusionMaterial.worldFocusDistance = v;
66
+ }
65
67
  this.focalLength.onValueChanged = v => dof.circleOfConfusionMaterial.worldFocusRange = v;
66
68
  this.aperture.onValueChanged = v => dof.bokehScale = v;
67
69
 
@@ -69,22 +71,8 @@ export class DepthOfField extends PostProcessingEffect {
69
71
  return [dof];
70
72
  }
71
73
 
72
-
73
74
  unapply() {
74
75
  }
75
76
 
76
-
77
- calculateFocusDistance(target) {
78
-
79
- const camera = this.context.mainCamera! as PerspectiveCamera;
80
- const distance = getWorldPosition(camera).distanceTo(target);
81
- // console.log(distance, camera.near, camera.far);
82
- return this.viewZToOrthographicDepth(-distance, camera.near, camera.far);
83
-
84
- }
85
-
86
- viewZToOrthographicDepth(viewZ, near, far) {
87
- return (viewZ + near) / (near - far);
88
- }
89
77
  }
90
78
  registerCustomEffectType("DepthOfField", DepthOfField);
@@ -118,6 +118,7 @@ export class PostProcessingHandler {
118
118
  // const hdrRenderTarget = new WebGLRenderTarget(window.innerWidth, window.innerHeight, { type: HalfFloatType });
119
119
  this._composer = new EffectComposer(renderer, {
120
120
  frameBufferType: HalfFloatType,
121
+ stencilBuffer: true,
121
122
  // multisampling: 2,
122
123
  });
123
124
  }
@@ -134,6 +135,7 @@ export class PostProcessingHandler {
134
135
  if (debug)
135
136
  console.log("Set effects or passes", camera, effectsOrPasses, composer);
136
137
 
138
+ // TODO: enforce correct order of effects (e.g. DOF before Bloom)
137
139
 
138
140
  const effects: Array<Effect> = [];
139
141
 
@@ -1,191 +1,213 @@
1
- import { Behaviour, GameObject } from "../Component";
2
- import { serializeable } from "../../engine/engine_serialization_decorator";
3
- import { getParam } from "../../engine/engine_utils";
4
- import { VolumeProfile } from "./VolumeProfile";
5
- import { EditorModification, IEditorModification as IEditorModificationReceiver } from "../../engine/engine_editor-sync";
6
- import { PostProcessingHandler } from "./PostProcessingHandler";
7
- import { PostProcessingEffect } from "./PostProcessingEffect";
8
- import { VolumeParameter } from "./VolumeParameter";
9
- import { getEditorModificationCache } from "../../engine/engine_editor-sync";
10
- import { isDevEnvironment } from "../../engine/debug";
11
-
12
- const debug = getParam("debugpost");
13
-
14
- export class Volume extends Behaviour implements IEditorModificationReceiver {
15
-
16
- @serializeable(VolumeProfile)
17
- sharedProfile?: VolumeProfile;
18
-
19
- private _postprocessing?: PostProcessingHandler;
20
- private _effects: PostProcessingEffect[] = [];
21
-
22
- awake() {
23
- // ensure the profile is initialized
24
- this.sharedProfile?.init();
25
-
26
- if (debug) {
27
- console.log(this);
28
- console.log("Press P to toggle post processing");
29
- window.addEventListener("keydown", (e) => {
30
- if (e.key === "p") {
31
- console.log("Toggle volume: " + this.name, !this.enabled);
32
- this.enabled = !this.enabled;
33
- }
34
- });
35
- }
36
- }
37
-
38
- onDisable() {
39
- this._postprocessing?.unapply();
40
- }
41
-
42
- onBeforeRender(): void {
43
- // Wait for the first frame to be rendered before creating because then we know we have a camera (issue 135)
44
- if (this.context.mainCamera) {
45
- if (!this._postprocessing || !this._postprocessing.isActive)
46
- this.apply();
47
- }
48
-
49
- if (!this.context.isInXR && this.context.composer && this.context.mainCamera) {
50
- this.context.composer.setRenderer(this.context.renderer);
51
- this.context.composer.setMainScene(this.context.scene);
52
- this.context.composer.setMainCamera(this.context.mainCamera);
53
- }
54
- }
55
-
56
- onDestroy(): void {
57
- this._postprocessing?.dispose();
58
- }
59
-
60
- private _lastApplyTime?: number;
61
- private _rapidApplyCount = 0;
62
-
63
- private apply() {
64
- if (debug) console.log("Apply PostProcessing", this, this.context.mainCamera?.name);
65
-
66
- if (isDevEnvironment()) {
67
- if (this._lastApplyTime !== undefined && Date.now() - this._lastApplyTime < 100) {
68
- this._rapidApplyCount++;
69
- if (this._rapidApplyCount === 5)
70
- console.warn("Detected rapid post processing modifications - this might be a bug", this);
71
- }
72
- this._lastApplyTime = Date.now();
73
- }
74
-
75
-
76
- this.unapply();
77
-
78
- this._effects.length = 0;
79
- // get from profile
80
- if (this.sharedProfile?.components) {
81
- this._effects.push(...this.sharedProfile.components);
82
- }
83
- // get additional effects
84
- const additionalComponents = this.gameObject.getComponentsInChildren(PostProcessingEffect);
85
- if (debug && additionalComponents?.length)
86
- console.log("Additional", additionalComponents);
87
- if (additionalComponents) {
88
- for (const comp of additionalComponents) {
89
- if (comp.active) this._effects.push(comp);
90
- }
91
- }
92
-
93
- if (this._effects.length > 0) {
94
- if (!this._postprocessing)
95
- this._postprocessing = new PostProcessingHandler(this.context);
96
- this._postprocessing.apply(this._effects);
97
- }
98
- }
99
-
100
- private unapply() {
101
- this._postprocessing?.unapply();
102
- }
103
-
104
-
105
- private tryApplyCache() {
106
- const cache = getEditorModificationCache();
107
- if (cache) {
108
- for (const entry of cache.values()) this.onEditorModification(entry);
109
- }
110
- }
111
-
112
-
113
- /** called from needle editor sync package if its active */
114
- onEditorModification(modification: EditorModification): void | boolean | undefined {
115
-
116
- if (modification.propertyName.startsWith("postprocessing.")) {
117
- if (!this._effects?.length) return;
118
- const path = modification.propertyName.split(".");
119
- if (path.length === 3 || path.length === 4) {
120
- const componentName = path[1];
121
- const propertyName = path[2];
122
- for (const comp of this._effects) {
123
- if (comp.typeName?.toLowerCase() === componentName.toLowerCase()) {
124
-
125
- if (propertyName === "active") {
126
- comp.active = modification.value;
127
- this.scheduleRecreate();
128
- return;
129
- }
130
-
131
- // cache the volume parameters
132
- if (!effectVolumeProperties.has(componentName)) {
133
- const volumeParameterKeys = new Array<string>();
134
- effectVolumeProperties.set(componentName, volumeParameterKeys);
135
- const keys = Object.keys(comp);
136
- for (const key of keys) {
137
- const prop = comp[key];
138
- if (prop instanceof VolumeParameter) {
139
- volumeParameterKeys.push(key);
140
- }
141
- }
142
- }
143
-
144
- if (effectVolumeProperties.has(componentName)) {
145
- const paramName = propertyName.toLowerCase();
146
- const volumeParameterKeys = effectVolumeProperties.get(componentName)!;
147
- for (const key of volumeParameterKeys) {
148
- if (key.toLowerCase() === paramName) {
149
- const prop = comp[key] as VolumeParameter;
150
- if (prop instanceof VolumeParameter) {
151
- const isActiveStateChange = path.length === 4 && path[3] === "active";
152
- if (isActiveStateChange) {
153
- prop.overrideState = modification.value;
154
- this.scheduleRecreate();
155
- }
156
- else if (prop && prop.value !== undefined) {
157
- prop.value = modification.value;
158
- }
159
- }
160
- return;
161
- }
162
- }
163
- }
164
-
165
- console.warn("Unknown modification", propertyName);
166
- return;
167
- }
168
- }
169
- }
170
- return true;
171
- }
172
-
173
- return false;
174
- }
175
-
176
- private _recreateId: number = -1;
177
- private scheduleRecreate() {
178
- // When the editor modifications come in with changed active effects we want/need to re-create the effects
179
- // We defer it slightly because multiple active changes could be made and we dont want to recreate the full effect stack multiple times
180
- const id = ++this._recreateId;
181
- setTimeout(() => {
182
- if (id !== this._recreateId) return;
183
- this.onDisable();
184
- this.onEnable();
185
- }, 200);
186
- }
187
-
188
- }
189
-
190
- /** cached VolumeParameter keys per object */
191
- const effectVolumeProperties: Map<string, string[]> = new Map<string, string[]>();
1
+ import { Behaviour, GameObject } from "../Component";
2
+ import { serializeable } from "../../engine/engine_serialization_decorator";
3
+ import { getParam } from "../../engine/engine_utils";
4
+ import { VolumeProfile } from "./VolumeProfile";
5
+ import { EditorModification, IEditorModification as IEditorModificationReceiver } from "../../engine/engine_editor-sync";
6
+ import { PostProcessingHandler } from "./PostProcessingHandler";
7
+ import { PostProcessingEffect } from "./PostProcessingEffect";
8
+ import { VolumeParameter } from "./VolumeParameter";
9
+ import { getEditorModificationCache } from "../../engine/engine_editor-sync";
10
+ import { isDevEnvironment } from "../../engine/debug";
11
+ import { EffectComposer } from "postprocessing";
12
+
13
+ const debug = getParam("debugpost");
14
+
15
+ export class Volume extends Behaviour implements IEditorModificationReceiver {
16
+
17
+ @serializeable(VolumeProfile)
18
+ sharedProfile?: VolumeProfile;
19
+
20
+ private _postprocessing?: PostProcessingHandler;
21
+ private _effects: PostProcessingEffect[] = [];
22
+
23
+ awake() {
24
+ // ensure the profile is initialized
25
+ this.sharedProfile?.init();
26
+
27
+ if (debug) {
28
+ console.log(this);
29
+ console.log("Press P to toggle post processing");
30
+ window.addEventListener("keydown", (e) => {
31
+ if (e.key === "p") {
32
+ console.log("Toggle volume: " + this.name, !this.enabled);
33
+ this.enabled = !this.enabled;
34
+ }
35
+ });
36
+ }
37
+ }
38
+
39
+ onDisable() {
40
+ this._postprocessing?.unapply();
41
+ }
42
+
43
+ onBeforeRender(): void {
44
+ if (!this.context.isInXR) {
45
+
46
+ if (this.context.composer && (this.context.composer instanceof EffectComposer) === false) {
47
+ return;
48
+ }
49
+
50
+ // Wait for the first frame to be rendered before creating because then we know we have a camera (issue 135)
51
+ if (this.context.mainCamera) {
52
+ if (!this._postprocessing || !this._postprocessing.isActive)
53
+ this.apply();
54
+ }
55
+
56
+ if (this.context.composer) {
57
+ this.context.composer.setRenderer(this.context.renderer);
58
+ this.context.composer.setMainScene(this.context.scene);
59
+ if (this.context.mainCamera)
60
+ this.context.composer.setMainCamera(this.context.mainCamera);
61
+ }
62
+ if (this.context.renderer) {
63
+ this.context.renderer.autoClear = false;
64
+ }
65
+ }
66
+ }
67
+
68
+ onDestroy(): void {
69
+ this._postprocessing?.dispose();
70
+ }
71
+
72
+ private _lastApplyTime?: number;
73
+ private _rapidApplyCount = 0;
74
+
75
+ private apply() {
76
+ if (debug) console.log("Apply PostProcessing", this, this.context.mainCamera?.name);
77
+
78
+ if (isDevEnvironment()) {
79
+ if (this._lastApplyTime !== undefined && Date.now() - this._lastApplyTime < 100) {
80
+ this._rapidApplyCount++;
81
+ if (this._rapidApplyCount === 5)
82
+ console.warn("Detected rapid post processing modifications - this might be a bug", this);
83
+ }
84
+ this._lastApplyTime = Date.now();
85
+ }
86
+
87
+
88
+ this.unapply();
89
+
90
+ this._effects.length = 0;
91
+ // get from profile
92
+ if (this.sharedProfile?.components) {
93
+ this._effects.push(...this.sharedProfile.components);
94
+ }
95
+ // get additional effects
96
+ const additionalComponents = this.gameObject.getComponentsInChildren(PostProcessingEffect);
97
+ if (debug && additionalComponents?.length)
98
+ console.log("Additional", additionalComponents);
99
+ if (additionalComponents) {
100
+ for (const comp of additionalComponents) {
101
+ if (comp.active) this._effects.push(comp);
102
+ }
103
+ }
104
+
105
+ if (this._effects.length > 0) {
106
+ if (!this._postprocessing)
107
+ this._postprocessing = new PostProcessingHandler(this.context);
108
+ this._postprocessing.apply(this._effects);
109
+ }
110
+
111
+ this._applyPostQueue();
112
+ }
113
+
114
+ private unapply() {
115
+ this._postprocessing?.unapply();
116
+ }
117
+
118
+
119
+ private _applyPostQueue() {
120
+ if (this._modificationQueue) {
121
+ for (const entry of this._modificationQueue.values()) this.onEditorModification(entry);
122
+ this._modificationQueue.clear();
123
+ }
124
+ }
125
+
126
+ /** called from needle editor sync package if its active */
127
+ onEditorModification(modification: EditorModification): void | boolean | undefined {
128
+
129
+ if (modification.propertyName.startsWith("postprocessing.")) {
130
+
131
+ if (!this._postprocessing) {
132
+ if (!this._modificationQueue) this._modificationQueue = new Map<string, EditorModification>();
133
+ this._modificationQueue.set(modification.propertyName, modification);
134
+ return true;
135
+ }
136
+
137
+ if (!this._effects?.length) return;
138
+ const path = modification.propertyName.split(".");
139
+ if (path.length === 3 || path.length === 4) {
140
+ const componentName = path[1];
141
+ const propertyName = path[2];
142
+ for (const comp of this._effects) {
143
+ if (comp.typeName?.toLowerCase() === componentName.toLowerCase()) {
144
+
145
+ if (propertyName === "active") {
146
+ comp.active = modification.value;
147
+ this.scheduleRecreate();
148
+ return;
149
+ }
150
+
151
+ // cache the volume parameters
152
+ if (!effectVolumeProperties.has(componentName)) {
153
+ const volumeParameterKeys = new Array<string>();
154
+ effectVolumeProperties.set(componentName, volumeParameterKeys);
155
+ const keys = Object.keys(comp);
156
+ for (const key of keys) {
157
+ const prop = comp[key];
158
+ if (prop instanceof VolumeParameter) {
159
+ volumeParameterKeys.push(key);
160
+ }
161
+ }
162
+ }
163
+
164
+ if (effectVolumeProperties.has(componentName)) {
165
+ const paramName = propertyName.toLowerCase();
166
+ const volumeParameterKeys = effectVolumeProperties.get(componentName)!;
167
+ for (const key of volumeParameterKeys) {
168
+ if (key.toLowerCase() === paramName) {
169
+ const prop = comp[key] as VolumeParameter;
170
+ if (prop instanceof VolumeParameter) {
171
+ const isActiveStateChange = path.length === 4 && path[3] === "active";
172
+ if (isActiveStateChange) {
173
+ prop.overrideState = modification.value;
174
+ this.scheduleRecreate();
175
+ }
176
+ else if (prop && prop.value !== undefined) {
177
+ prop.value = modification.value;
178
+ }
179
+ }
180
+ return;
181
+ }
182
+ }
183
+ }
184
+
185
+ console.warn("Unknown modification", propertyName);
186
+ return;
187
+ }
188
+ }
189
+ }
190
+ return true;
191
+ }
192
+
193
+ return false;
194
+ }
195
+
196
+ private _modificationQueue?: Map<string, EditorModification>;
197
+
198
+ private _recreateId: number = -1;
199
+ private scheduleRecreate() {
200
+ // When the editor modifications come in with changed active effects we want/need to re-create the effects
201
+ // We defer it slightly because multiple active changes could be made and we dont want to recreate the full effect stack multiple times
202
+ const id = ++this._recreateId;
203
+ setTimeout(() => {
204
+ if (id !== this._recreateId) return;
205
+ this.onDisable();
206
+ this.onEnable();
207
+ }, 200);
208
+ }
209
+
210
+ }
211
+
212
+ /** cached VolumeParameter keys per object */
213
+ const effectVolumeProperties: Map<string, string[]> = new Map<string, string[]>();