Needle Engine

Changes between version 3.42.0-beta and 3.43.0-beta
Files changed (20) hide show
  1. src/engine/webcomponents/api.ts +2 -1
  2. src/engine-components/codegen/components.ts +2 -1
  3. src/engine/engine_context.ts +3 -4
  4. src/engine/engine_tonemapping.ts +1 -0
  5. src/engine/engine_utils_format.ts +1 -1
  6. src/engine-components/postprocessing/index.ts +2 -1
  7. src/engine/webcomponents/needle-button.ts +1 -1
  8. src/engine/webcomponents/needle menu/needle-menu-spatial.ts +10 -4
  9. src/engine-components/postprocessing/PostProcessingEffect.ts +14 -1
  10. src/engine-components/postprocessing/PostProcessingHandler.ts +4 -0
  11. src/engine/codegen/register_types.ts +4 -2
  12. src/engine-components/postprocessing/Effects/Tonemapping.ts +4 -4
  13. src/engine-components/export/usdz/USDZExporter.ts +1 -1
  14. src/engine-components/postprocessing/Volume.ts +91 -23
  15. src/engine-components/postprocessing/VolumeProfile.ts +13 -1
  16. src/engine-components/webxr/WebXR.ts +1 -1
  17. src/engine/webcomponents/WebXRButtons.ts +4 -4
  18. src/engine-components/postprocessing/Effects/EffectWrapper.ts +22 -0
  19. src/engine-components/postprocessing/Effects/Sharpening.ts +102 -0
  20. src/engine-components/postprocessing/utils.ts +50 -0
src/engine/webcomponents/api.ts CHANGED
@@ -3,4 +3,5 @@
3
3
  export { ButtonsFactory } from "./buttons.js"
4
4
  export * from "./icons.js"
5
5
  export { type NeedleMenuPostMessageModel } from "./needle menu/needle-menu.js"
6
- export { NeedleButtonElement } from "./needle-button.js"
6
+ export { NeedleButtonElement } from "./needle-button.js"
7
+ export { WebXRButtonFactory } from "./WebXRButtons.js"
src/engine-components/codegen/components.ts CHANGED
@@ -62,6 +62,7 @@
62
62
  export { DragControls } from "../DragControls.js";
63
63
  export { DropListener } from "../DropListener.js";
64
64
  export { Duplicatable } from "../Duplicatable.js";
65
+ export { EffectWrapper } from "../postprocessing/Effects/EffectWrapper.js";
65
66
  export { EmissionModule } from "../ParticleSystemModules.js";
66
67
  export { EmphasizeOnClick } from "../export/usdz/extensions/behavior/BehaviourComponents.js";
67
68
  export { EventList } from "../EventList.js";
@@ -147,6 +148,7 @@
147
148
  export { SetActiveOnClick } from "../export/usdz/extensions/behavior/BehaviourComponents.js";
148
149
  export { ShadowCatcher } from "../ShadowCatcher.js";
149
150
  export { ShapeModule } from "../ParticleSystemModules.js";
151
+ export { SharpeningEffect } from "../postprocessing/Effects/Sharpening.js";
150
152
  export { SignalAsset } from "../timeline/SignalAsset.js";
151
153
  export { SignalReceiver } from "../timeline/SignalAsset.js";
152
154
  export { SignalReceiverEvent } from "../timeline/SignalAsset.js";
@@ -204,7 +206,6 @@
204
206
  export { WebARCameraBackground } from "../webxr/WebARCameraBackground.js";
205
207
  export { WebARSessionRoot } from "../webxr/WebARSessionRoot.js";
206
208
  export { WebXR } from "../webxr/WebXR.js";
207
- export { WebXRButtonFactory } from "../../engine/webcomponents/WebXRButtons.js";
208
209
  export { WebXRImageTracking } from "../webxr/WebXRImageTracking.js";
209
210
  export { WebXRImageTrackingModel } from "../webxr/WebXRImageTracking.js";
210
211
  export { WebXRPlaneTracking } from "../webxr/WebXRPlaneTracking.js";
src/engine/engine_context.ts CHANGED
@@ -1,11 +1,10 @@
1
1
  import { EffectComposer, RenderPass } from "postprocessing";
2
2
  import {
3
+ AgXToneMapping,
3
4
  BufferGeometry, Cache, Camera, Clock, Color, DepthTexture, Group,
4
5
  Material, NearestFilter, NoToneMapping, Object3D, PCFSoftShadowMap,
5
6
  PerspectiveCamera, RGBAFormat, Scene, SRGBColorSpace,
6
- Texture, WebGLRenderer, type WebGLRendererParameters, WebGLRenderTarget, type WebXRArrayCamera,
7
- AgXToneMapping
8
- } from 'three';
7
+ Texture, WebGLRenderer, type WebGLRendererParameters, WebGLRenderTarget, type WebXRArrayCamera} from 'three';
9
8
  import * as Stats from 'three/examples/jsm/libs/stats.module.js';
10
9
 
11
10
  import { isDevEnvironment, LogType, showBalloonError, showBalloonMessage, showBalloonWarning } from './debug/index.js';
@@ -29,12 +28,12 @@
29
28
  import { RendererData as SceneLighting } from './engine_scenelighting.js';
30
29
  import { logHierarchy } from './engine_three_utils.js';
31
30
  import { Time } from './engine_time.js';
31
+ import { patchTonemapping } from './engine_tonemapping.js';
32
32
  import type { CoroutineData, GLTF, ICamera, IComponent, IContext, ILight, LoadedGLTF, Vec2 } from "./engine_types.js";
33
33
  import * as utils from "./engine_utils.js";
34
34
  import { delay, getParam } from './engine_utils.js';
35
35
  import type { INeedleXRSessionEventReceiver, NeedleXRSession } from './engine_xr.js';
36
36
  import { NeedleMenu } from './webcomponents/needle menu/needle-menu.js';
37
- import { patchTonemapping } from './engine_tonemapping.js';
38
37
 
39
38
 
40
39
  const debug = utils.getParam("debugcontext");
src/engine/engine_tonemapping.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { ShaderChunk } from "three";
2
+
2
3
  import type { Context } from "./engine_setup";
3
4
 
4
5
  let patchedTonemapping = false;
src/engine/engine_utils_format.ts CHANGED
@@ -25,7 +25,7 @@
25
25
  // We want to save on requests so we first check the file extension if there's any
26
26
  // In some scenarios we might not have one (e.g. if we're dealing with blob: files or if the URL doesn't contain the filename)
27
27
  // In that case we need to check the header
28
- let _url = url;
28
+ const _url = url;
29
29
  // if (!_url.startsWith("http") && !url.startsWith("blob:")) {
30
30
  // // _url = "file:" + url;
31
31
  // }
src/engine-components/postprocessing/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./PostProcessingEffect.js";
2
2
  export * from "./PostProcessingHandler.js"
3
+ export { PostProcessingManager } from "./Volume.js"
3
4
  export * from "./VolumeParameter.js"
4
- export * from "./VolumeProfile.js";
5
+ export * from "./VolumeProfile.js";
src/engine/webcomponents/needle-button.ts CHANGED
@@ -1,5 +1,5 @@
1
+ import { iconFontUrl, loadFont } from "./fonts.js";
1
2
  import { WebXRButtonFactory } from "./WebXRButtons.js";
2
- import { iconFontUrl, loadFont } from "./fonts.js";
3
3
 
4
4
  const htmlTagName = "needle-button";
5
5
 
src/engine/webcomponents/needle menu/needle-menu-spatial.ts CHANGED
@@ -127,10 +127,16 @@
127
127
  existing.add();
128
128
  return;
129
129
  }
130
- const button = node as HTMLButtonElement;
131
- const spatialButton = this.createButton(menu, button);
132
- this.htmlButtonsMap.set(button, spatialButton);
133
- spatialButton.add();
130
+ if (node instanceof HTMLButtonElement) {
131
+ const spatialButton = this.createButton(menu, node);
132
+ this.htmlButtonsMap.set(node, spatialButton);
133
+ spatialButton.add();
134
+ }
135
+ else if (node instanceof HTMLSlotElement) {
136
+ node.assignedNodes().forEach((node) => {
137
+ this.createButtonFromHTMLNode(node);
138
+ });
139
+ }
134
140
  }
135
141
 
136
142
  private readonly _menuTarget: Object3D = new Object3D();
src/engine-components/postprocessing/PostProcessingEffect.ts CHANGED
@@ -5,6 +5,7 @@
5
5
  import type { ISerializable, SerializationContext } from "../../engine/engine_serialization_core.js";
6
6
  import { getParam } from "../../engine/engine_utils.js";
7
7
  import { Component } from "../Component.js";
8
+ import { getPostProcessingManager,IPostProcessingManager } from "./utils.js";
8
9
  import { VolumeParameter } from "./VolumeParameter.js";
9
10
 
10
11
  const debug = getParam("debugpost");
@@ -44,29 +45,41 @@
44
45
  */
45
46
  export abstract class PostProcessingEffect extends Component implements IEffectProvider, ISerializable, IEditorModification {
46
47
 
48
+ get isPostProcessingEffect() { return true; }
49
+
47
50
  constructor(params: any = undefined) {
48
51
  super();
52
+ this.ensureVolumeParameters();
49
53
  if (params) {
50
- this.ensureVolumeParameters();
51
54
  for (const key of Object.keys(params)) {
52
55
  const value = params[key];
53
56
  const param = this[key];
54
57
  if (param instanceof VolumeParameter) {
55
58
  param.value = value;
56
59
  }
60
+ // allow assigning values to properties that are not VolumeParameters
61
+ // this is useful when effects are created in code
62
+ else if(param !== undefined){
63
+ this[key] = value;
64
+ }
57
65
  }
58
66
  }
59
67
  }
60
68
 
61
69
  abstract get typeName(): string;
62
70
 
71
+ private _manager: IPostProcessingManager | null = null;
72
+
63
73
  onEnable(): void {
74
+ this._manager = getPostProcessingManager(this);
75
+ this._manager?.addEffect(this);
64
76
  // Dont override the serialized value by enabling (we could also just disable this component / map enabled to active)
65
77
  if (this.__internalDidAwakeAndStart)
66
78
  this.active = true;
67
79
  }
68
80
 
69
81
  onDisable(): void {
82
+ this._manager?.removeEffect(this);
70
83
  this.active = false;
71
84
  }
72
85
 
src/engine-components/postprocessing/PostProcessingHandler.ts CHANGED
@@ -7,7 +7,9 @@
7
7
  import type { Constructor } from "../../engine/engine_types.js";
8
8
  import { getParam, isMobileDevice } from "../../engine/engine_utils.js";
9
9
  import { Camera } from "../Camera.js";
10
+ import { Antialiasing } from "./Effects/Antialiasing.js";
10
11
  import { ColorAdjustments } from "./Effects/ColorAdjustments.js";
12
+ import { SharpeningEffect } from "./Effects/Sharpening.js";
11
13
  import { ToneMapping } from "./Effects/Tonemapping.js";
12
14
  import { PostProcessingEffect } from "./PostProcessingEffect.js";
13
15
 
@@ -270,4 +272,6 @@
270
272
  HueSaturationEffect,
271
273
  BrightnessContrastEffect,
272
274
  PixelationEffect,
275
+ SharpeningEffect,
276
+ Antialiasing
273
277
  ];
src/engine/codegen/register_types.ts CHANGED
@@ -64,6 +64,7 @@
64
64
  import { DragControls } from "../../engine-components/DragControls.js";
65
65
  import { DropListener } from "../../engine-components/DropListener.js";
66
66
  import { Duplicatable } from "../../engine-components/Duplicatable.js";
67
+ import { EffectWrapper } from "../../engine-components/postprocessing/Effects/EffectWrapper.js";
67
68
  import { EmissionModule } from "../../engine-components/ParticleSystemModules.js";
68
69
  import { EmphasizeOnClick } from "../../engine-components/export/usdz/extensions/behavior/BehaviourComponents.js";
69
70
  import { EventList } from "../../engine-components/EventList.js";
@@ -152,6 +153,7 @@
152
153
  import { SetActiveOnClick } from "../../engine-components/export/usdz/extensions/behavior/BehaviourComponents.js";
153
154
  import { ShadowCatcher } from "../../engine-components/ShadowCatcher.js";
154
155
  import { ShapeModule } from "../../engine-components/ParticleSystemModules.js";
156
+ import { SharpeningEffect } from "../../engine-components/postprocessing/Effects/Sharpening.js";
155
157
  import { SignalAsset } from "../../engine-components/timeline/SignalAsset.js";
156
158
  import { SignalReceiver } from "../../engine-components/timeline/SignalAsset.js";
157
159
  import { SignalReceiverEvent } from "../../engine-components/timeline/SignalAsset.js";
@@ -209,7 +211,6 @@
209
211
  import { WebARCameraBackground } from "../../engine-components/webxr/WebARCameraBackground.js";
210
212
  import { WebARSessionRoot } from "../../engine-components/webxr/WebARSessionRoot.js";
211
213
  import { WebXR } from "../../engine-components/webxr/WebXR.js";
212
- import { WebXRButtonFactory } from "../webcomponents/WebXRButtons.js";
213
214
  import { WebXRImageTracking } from "../../engine-components/webxr/WebXRImageTracking.js";
214
215
  import { WebXRImageTrackingModel } from "../../engine-components/webxr/WebXRImageTracking.js";
215
216
  import { WebXRPlaneTracking } from "../../engine-components/webxr/WebXRPlaneTracking.js";
@@ -284,6 +285,7 @@
284
285
  TypeStore.add("DragControls", DragControls);
285
286
  TypeStore.add("DropListener", DropListener);
286
287
  TypeStore.add("Duplicatable", Duplicatable);
288
+ TypeStore.add("EffectWrapper", EffectWrapper);
287
289
  TypeStore.add("EmissionModule", EmissionModule);
288
290
  TypeStore.add("EmphasizeOnClick", EmphasizeOnClick);
289
291
  TypeStore.add("EventList", EventList);
@@ -372,6 +374,7 @@
372
374
  TypeStore.add("SetActiveOnClick", SetActiveOnClick);
373
375
  TypeStore.add("ShadowCatcher", ShadowCatcher);
374
376
  TypeStore.add("ShapeModule", ShapeModule);
377
+ TypeStore.add("SharpeningEffect", SharpeningEffect);
375
378
  TypeStore.add("SignalAsset", SignalAsset);
376
379
  TypeStore.add("SignalReceiver", SignalReceiver);
377
380
  TypeStore.add("SignalReceiverEvent", SignalReceiverEvent);
@@ -429,7 +432,6 @@
429
432
  TypeStore.add("WebARCameraBackground", WebARCameraBackground);
430
433
  TypeStore.add("WebARSessionRoot", WebARSessionRoot);
431
434
  TypeStore.add("WebXR", WebXR);
432
- TypeStore.add("WebXRButtonFactory", WebXRButtonFactory);
433
435
  TypeStore.add("WebXRImageTracking", WebXRImageTracking);
434
436
  TypeStore.add("WebXRImageTrackingModel", WebXRImageTrackingModel);
435
437
  TypeStore.add("WebXRPlaneTracking", WebXRPlaneTracking);
src/engine-components/postprocessing/Effects/Tonemapping.ts CHANGED
@@ -8,10 +8,10 @@
8
8
 
9
9
  export enum TonemappingMode {
10
10
  None = 0,
11
- Neutral = 1, // Neutral tonemapper, close to Reinhard
12
- ACES = 2, // ACES Filmic reference tonemapper (custom approximation)
13
- AgX = 3, // AgX Filmic tonemapper
14
- KhronosNeutral = 4, // PBR Neural tonemapper
11
+ Neutral = 1, // Neutral tonemapper, close to Reinhard
12
+ ACES = 2, // ACES Filmic reference tonemapper (custom approximation)
13
+ AgX = 3, // AgX Filmic tonemapper
14
+ KhronosNeutral = 4, // PBR Neural tonemapper
15
15
  }
16
16
 
17
17
  export class ToneMapping extends PostProcessingEffect {
src/engine-components/export/usdz/USDZExporter.ts CHANGED
@@ -6,12 +6,12 @@
6
6
  import { serializable } from "../../../engine/engine_serialization.js";
7
7
  import { getFormattedDate, Progress } from "../../../engine/engine_time_utils.js";
8
8
  import { getParam, isiOS, isMobileDevice, isSafari } from "../../../engine/engine_utils.js";
9
+ import { WebXRButtonFactory } from "../../../engine/webcomponents/WebXRButtons.js";
9
10
  import { Behaviour, GameObject } from "../../Component.js";
10
11
  import { Renderer } from "../../Renderer.js"
11
12
  import { SpriteRenderer } from "../../SpriteRenderer.js";
12
13
  import { WebARSessionRoot } from "../../webxr/WebARSessionRoot.js";
13
14
  import { WebXR } from "../../webxr/WebXR.js";
14
- import { WebXRButtonFactory } from "../../../engine/webcomponents/WebXRButtons.js";
15
15
  import { XRState, XRStateFlag } from "../../webxr/XRFlag.js";
16
16
  import type { IUSDExporterExtension } from "./Extension.js";
17
17
  import { AnimationExtension } from "./extensions/Animation.js"
src/engine-components/postprocessing/Volume.ts CHANGED
@@ -1,30 +1,93 @@
1
- import { EffectComposer } from "postprocessing";
1
+ import { Effect, EffectComposer } from "postprocessing";
2
2
 
3
3
  import { isDevEnvironment, showBalloonMessage } from "../../engine/debug/index.js";
4
4
  import type { EditorModification, IEditorModification as IEditorModificationReceiver } from "../../engine/engine_editor-sync.js";
5
5
  import { serializeable } from "../../engine/engine_serialization_decorator.js";
6
6
  import { getParam } from "../../engine/engine_utils.js";
7
7
  import { Behaviour } from "../Component.js";
8
+ import { EffectWrapper } from "./Effects/EffectWrapper.js";
8
9
  import { PostProcessingEffect } from "./PostProcessingEffect.js";
9
10
  import { PostProcessingHandler } from "./PostProcessingHandler.js";
11
+ import { IPostProcessingManager, setPostprocessingManagerType } from "./utils.js";
10
12
  import { VolumeParameter } from "./VolumeParameter.js";
11
13
  import { VolumeProfile } from "./VolumeProfile.js";
12
14
 
13
15
  const debug = getParam("debugpost");
14
16
 
15
- /** Handles PostProcessing effects */
16
- export class Volume extends Behaviour implements IEditorModificationReceiver {
17
17
 
18
- @serializeable(VolumeProfile)
19
- sharedProfile?: VolumeProfile;
18
+ /** The Volume/PostprocessingManager component is responsible for managing post processing effects.
19
+ * Add this component to any object in your scene to enable post processing effects.
20
+ *
21
+ * @example Add bloom
22
+ * ```ts
23
+ * const volume = new Volume();
24
+ * volume.addEffect(new BloomEffect({
25
+ * intensity: 3,
26
+ * luminanceThreshold: .2
27
+ * }));
28
+ * gameObject.addComponent(volume);
29
+ * ```
30
+ *
31
+ * @example Remove bloom
32
+ * ```ts
33
+ * volume.removeEffect(bloom);
34
+ * ```
35
+ *
36
+ * @example Add pixelation
37
+ * ```ts
38
+ * const pixelation = new PixelationEffect();
39
+ * pixelation.granularity.value = 10;
40
+ * volume.addEffect(pixelation);
41
+ * ```
42
+ */
43
+ export class Volume extends Behaviour implements IEditorModificationReceiver, IPostProcessingManager {
20
44
 
45
+ get isPostProcessingManager() {
46
+ return true;
47
+ }
48
+
21
49
  /** Currently active postprocessing effects */
22
50
  get effects() {
23
- return this._effects;
51
+ return this._activeEffects;
24
52
  }
25
53
 
54
+ @serializeable(VolumeProfile)
55
+ sharedProfile?: VolumeProfile;
56
+
57
+ /**
58
+ * Add a post processing effect to the stack and schedules the effect stack to be re-created.
59
+ */
60
+ addEffect<T extends PostProcessingEffect | Effect>(effect: T): T {
61
+
62
+ let entry = effect as PostProcessingEffect;
63
+ if (entry instanceof Effect) {
64
+ entry = new EffectWrapper(entry);
65
+ }
66
+ if (this._effects.includes(entry)) return effect;
67
+ this._effects.push(entry);
68
+ this._isDirty = true;
69
+ return effect;
70
+ }
71
+ removeEffect<T extends PostProcessingEffect | Effect>(effect: T): T {
72
+
73
+ let index = -1;
74
+ if (effect instanceof Effect) {
75
+ index = this._effects.findIndex(e => e instanceof EffectWrapper && e.effect === effect);
76
+ }
77
+ else
78
+ index = this._effects.indexOf(effect);
79
+
80
+ if (index !== -1) {
81
+ this._effects.splice(index, 1);
82
+ this._isDirty = true;
83
+ return effect;
84
+ }
85
+ return effect;
86
+ }
87
+
26
88
  private _postprocessing?: PostProcessingHandler;
27
- private _effects: PostProcessingEffect[] = [];
89
+ private readonly _activeEffects: PostProcessingEffect[] = [];
90
+ private readonly _effects: PostProcessingEffect[] = [];
28
91
 
29
92
  /**
30
93
  * When dirty the post processing effects will be re-applied
@@ -67,12 +130,12 @@
67
130
 
68
131
  // Wait for the first frame to be rendered before creating because then we know we have a camera (issue 135)
69
132
  if (this.context.mainCamera) {
70
- if (!this._postprocessing || !this._postprocessing.isActive || this._isDirty) {
133
+ if (this._isDirty) {
71
134
  this.apply();
72
135
  }
73
136
  }
74
137
 
75
- if (this.context.composer) {
138
+ if (this.context.composer && this._postprocessing?.composer === this.context.composer) {
76
139
  this.context.composer.setRenderer(this.context.renderer);
77
140
  this.context.composer.setMainScene(this.context.scene);
78
141
  if (this.context.mainCamera)
@@ -105,25 +168,25 @@
105
168
  this._isDirty = false;
106
169
  this.unapply();
107
170
 
108
- this._effects.length = 0;
171
+ this._activeEffects.length = 0;
109
172
  // get from profile
110
173
  if (this.sharedProfile?.components) {
111
- this._effects.push(...this.sharedProfile.components);
112
- }
113
- // get additional effects
114
- const additionalComponents = this.gameObject.getComponentsInChildren(PostProcessingEffect);
115
- if (debug && additionalComponents?.length)
116
- console.log("Additional", additionalComponents);
117
- if (additionalComponents) {
118
- for (const comp of additionalComponents) {
119
- if (comp.active) this._effects.push(comp);
174
+ const comps = this.sharedProfile.components;
175
+ for (const effect of comps) {
176
+ if (effect.active && !this._activeEffects.includes(effect))
177
+ this._activeEffects.push(effect);
120
178
  }
121
179
  }
180
+ // add effects registered via code
181
+ for (const effect of this._effects) {
182
+ if (effect.active && !this._activeEffects.includes(effect))
183
+ this._activeEffects.push(effect);
184
+ }
122
185
 
123
- if (this._effects.length > 0) {
186
+ if (this._activeEffects.length > 0) {
124
187
  if (!this._postprocessing)
125
188
  this._postprocessing = new PostProcessingHandler(this.context);
126
- this._postprocessing.apply(this._effects);
189
+ this._postprocessing.apply(this._activeEffects);
127
190
  this._applyPostQueue();
128
191
  }
129
192
 
@@ -153,12 +216,12 @@
153
216
  return true;
154
217
  }
155
218
 
156
- if (!this._effects?.length) return;
219
+ if (!this._activeEffects?.length) return;
157
220
  const path = modification.propertyName.split(".");
158
221
  if (path.length === 3 || path.length === 4) {
159
222
  const componentName = path[1];
160
223
  const propertyName = path[2];
161
- for (const comp of this._effects) {
224
+ for (const comp of this._activeEffects) {
162
225
  if (comp.typeName?.toLowerCase() === componentName.toLowerCase()) {
163
226
 
164
227
  if (propertyName === "active") {
@@ -230,3 +293,8 @@
230
293
 
231
294
  /** cached VolumeParameter keys per object */
232
295
  const effectVolumeProperties: Map<string, string[]> = new Map<string, string[]>();
296
+
297
+
298
+ setPostprocessingManagerType(Volume);
299
+
300
+ export { Volume as PostProcessingManager };
src/engine-components/postprocessing/VolumeProfile.ts CHANGED
@@ -30,12 +30,24 @@
30
30
  /** @internal */
31
31
  export class VolumeProfile {
32
32
 
33
+ /** effects added to the volume */
33
34
  @serializeable([d => resolveComponentType(d), PostProcessingEffect])
34
35
  components: PostProcessingEffect[] = [];
35
36
 
36
- /** call init on all components */
37
+ /**
38
+ * call init on all components
39
+ * @hidden
40
+ **/
37
41
  init() {
38
42
  this.components?.forEach(c => c.init());
39
43
  }
44
+
45
+ addEffect(effect: PostProcessingEffect) {
46
+ this.components.push(effect);
47
+ }
48
+ removeEffect(effect: PostProcessingEffect) {
49
+ const idx = this.components.indexOf(effect);
50
+ if (idx >= 0) this.components.splice(idx, 1);
51
+ }
40
52
  }
41
53
 
src/engine-components/webxr/WebXR.ts CHANGED
@@ -8,6 +8,7 @@
8
8
  import { type NeedleXREventArgs, NeedleXRSession } from "../../engine/engine_xr.js";
9
9
  import { ButtonsFactory } from "../../engine/webcomponents/buttons.js";
10
10
  import { getIconElement } from "../../engine/webcomponents/icons.js";
11
+ import { WebXRButtonFactory } from "../../engine/webcomponents/WebXRButtons.js";
11
12
  import { PlayerSync } from "../../engine-components-experimental/networking/PlayerSync.js";
12
13
  import { Behaviour, GameObject } from "../Component.js";
13
14
  import { USDZExporter } from "../export/usdz/USDZExporter.js";
@@ -17,7 +18,6 @@
17
18
  import { XRControllerModel } from "./controllers/XRControllerModel.js";
18
19
  import { XRControllerMovement } from "./controllers/XRControllerMovement.js";
19
20
  import { WebARSessionRoot } from "./WebARSessionRoot.js";
20
- import { WebXRButtonFactory } from "../../engine/webcomponents/WebXRButtons.js";
21
21
  import { XRState, XRStateFlag } from "./XRFlag.js";
22
22
 
23
23
  const debug = getParam("debugwebxr");
src/engine/webcomponents/WebXRButtons.ts CHANGED
@@ -1,12 +1,12 @@
1
+ import { USDZExporter } from "../../engine-components/export/usdz/USDZExporter.js";
1
2
  import { isDevEnvironment, showBalloonMessage } from "../debug/index.js";
3
+ import { findObjectOfType } from "../engine_components.js";
4
+ import { Context } from "../engine_setup.js";
2
5
  import { isMozillaXR } from "../engine_utils.js";
3
6
  import { NeedleXRSession } from "../engine_xr.js";
7
+ import { onXRSessionEnd, onXRSessionStart } from "../xr/events.js";
4
8
  import { ButtonsFactory } from "./buttons.js";
5
9
  import { getIconElement } from "./icons.js";
6
- import { onXRSessionEnd, onXRSessionStart } from "../xr/events.js";
7
- import { findObjectOfType } from "../engine_components.js";
8
- import { USDZExporter } from "../../engine-components/export/usdz/USDZExporter.js";
9
- import { Context } from "../engine_setup.js";
10
10
 
11
11
  // TODO: move these buttons into their own web components so their logic is encapsulated (e.g. the CSS animation when a xr session is requested)
12
12
 
src/engine-components/postprocessing/Effects/EffectWrapper.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { Effect } from "postprocessing";
2
+
3
+ import { EffectProviderResult, PostProcessingEffect } from "../PostProcessingEffect.js";
4
+
5
+ export class EffectWrapper extends PostProcessingEffect {
6
+
7
+ readonly effect: Effect;
8
+
9
+ constructor(effect: Effect) {
10
+ super();
11
+ this.effect = effect;
12
+ }
13
+
14
+ get typeName(): string {
15
+ return this.effect.constructor.name;
16
+ }
17
+
18
+ onCreateEffect(): EffectProviderResult | undefined {
19
+ return this.effect;
20
+ }
21
+
22
+ }
src/engine-components/postprocessing/Effects/Sharpening.ts ADDED
@@ -0,0 +1,102 @@
1
+ import { BlendFunction, Effect } from "postprocessing";
2
+ import { Uniform } from "three";
3
+
4
+ import { serializable } from "../../../engine/engine_serialization.js";
5
+ import { PostProcessingEffect } from "../PostProcessingEffect.js";
6
+
7
+ export class SharpeningEffect extends PostProcessingEffect {
8
+
9
+ get typeName() {
10
+ return "Sharpening";
11
+ }
12
+
13
+ private _effect?: _SharpeningEffect;
14
+
15
+ onCreateEffect() {
16
+ return this.effect;
17
+ }
18
+
19
+ private get effect() {
20
+ this._effect ??= new _SharpeningEffect();
21
+ return this._effect;
22
+ }
23
+
24
+ @serializable()
25
+ set amount(value: number) {
26
+ this.effect.uniforms.get("amount")!.value = value;
27
+ }
28
+ get amount() {
29
+ return this.effect.uniforms.get("amount")!.value;
30
+ }
31
+
32
+ @serializable()
33
+ set radius(value: number) {
34
+ this.effect.uniforms.get("radius")!.value = value;
35
+ }
36
+ get radius() {
37
+ return this.effect.uniforms.get("radius")!.value;
38
+ }
39
+
40
+ // @serializable()
41
+ // set threshold(value: number) {
42
+ // this.effect.uniforms.get("threshold")!.value = value;
43
+ // }
44
+ // get threshold() {
45
+ // return this.effect.uniforms.get("threshold")!.value;
46
+ // }
47
+
48
+ }
49
+
50
+
51
+ const vert = `
52
+ void mainSupport() {
53
+ vUv = uv;
54
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
55
+ }
56
+ `
57
+
58
+ const frag = `
59
+ uniform sampler2D tDiffuse;
60
+ uniform float amount;
61
+ uniform float threshold;
62
+ uniform float radius;
63
+
64
+ void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) {
65
+ float tx = 1.0 / resolution.x;
66
+ float ty = 1.0 / resolution.y;
67
+ vec2 texelSize = vec2(tx, ty);
68
+
69
+ vec4 color = texture2D(tDiffuse, uv);
70
+ vec4 blurred = vec4(0.0);
71
+ float total = 0.0;
72
+ for (float x = -radius; x <= radius; x++) {
73
+ for (float y = -radius; y <= radius; y++) {
74
+ vec2 offset = vec2(x, y) * texelSize;
75
+ vec4 diffuse = texture2D(tDiffuse, uv + offset);
76
+ float weight = exp(-length(offset) * amount);
77
+ blurred += diffuse * weight;
78
+ total += weight;
79
+ }
80
+ }
81
+ blurred /= total;
82
+ vec4 sharp = inputColor + (inputColor - blurred) * amount;
83
+ // float luma = dot(inputColor.rgb, vec3(0.299, 0.587, 0.114));
84
+ // float blend = smoothstep(threshold, 1.0, luma);
85
+ outputColor = sharp; //mix(inputColor, sharp, blend);
86
+
87
+ }
88
+ `
89
+
90
+ class _SharpeningEffect extends Effect {
91
+ constructor() {
92
+ super("Sharpening", frag, {
93
+ vertexShader: vert,
94
+ blendFunction: BlendFunction.NORMAL,
95
+ uniforms: new Map<string, Uniform<any>>([
96
+ ["amount", new Uniform(.8)],
97
+ ["radius", new Uniform(.5)],
98
+ // ["threshold", new Uniform(0)],
99
+ ]),
100
+ });
101
+ }
102
+ }
src/engine-components/postprocessing/utils.ts ADDED
@@ -0,0 +1,50 @@
1
+ import { Object3D } from "three";
2
+
3
+ import { isDevEnvironment } from "../../engine/debug/index.js";
4
+ import { addComponent } from "../../engine/engine_components.js";
5
+ import { foreachComponentEnumerator } from "../../engine/engine_gameobject.js";
6
+ import { ConstructorConcrete, IComponent } from "../../engine/engine_types.js";
7
+ import { getParam } from "../../engine/engine_utils.js";
8
+ import { type PostProcessingEffect } from "./PostProcessingEffect.js";
9
+
10
+ export const debug = getParam("debugpost");
11
+
12
+ export type IPostProcessingManager = IComponent & {
13
+ get isPostProcessingManager(): boolean;
14
+ addEffect(effect: PostProcessingEffect): void;
15
+ removeEffect(effect: PostProcessingEffect): void;
16
+ }
17
+
18
+
19
+ let PostprocessingManagerType: ConstructorConcrete<IPostProcessingManager> | null = null;
20
+
21
+ export function setPostprocessingManagerType(type: ConstructorConcrete<IPostProcessingManager>) {
22
+ PostprocessingManagerType = type;
23
+ }
24
+
25
+ export function getPostProcessingManager(effect: PostProcessingEffect): IPostProcessingManager | null {
26
+ let manager: IPostProcessingManager | null = null;
27
+ let obj = effect.gameObject as Object3D | null;
28
+ while (obj) {
29
+ for (const comp of foreachComponentEnumerator(obj)) {
30
+ if ((comp as unknown as IPostProcessingManager).isPostProcessingManager === true) {
31
+ manager = comp as unknown as IPostProcessingManager;
32
+ break;
33
+ }
34
+ }
35
+ obj = obj.parent;
36
+ }
37
+ if (!manager) {
38
+ if (PostprocessingManagerType) {
39
+ if (debug)
40
+ console.warn("Adding postprocessing manager to the scene.");
41
+ const scene = effect.scene;
42
+ manager = addComponent(scene, PostprocessingManagerType);
43
+ }
44
+ else {
45
+ if (isDevEnvironment())
46
+ console.warn("No post processing manager found");
47
+ }
48
+ }
49
+ return manager;
50
+ }