Skip to content

Commit d180844

Browse files
committed
create PostEffectsRenderer (wip)
1 parent 1808b19 commit d180844

File tree

7 files changed

+84
-48
lines changed

7 files changed

+84
-48
lines changed

packages/twopoint5d-elements/src/components/Stage2DElement.ts

+8-27
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {consume} from '@lit/context';
22
import {eventize, type Eventize} from '@spearwolf/eventize';
3-
import {createEffect, createSignal, value, type SignalFuncs, type SignalReader} from '@spearwolf/signalize';
3+
import {createEffect, type SignalReader} from '@spearwolf/signalize';
4+
import {signal, signalReader} from '@spearwolf/signalize/decorators';
45
import {
56
OrthographicProjection,
67
ParallaxProjection,
@@ -53,19 +54,8 @@ export class Stage2DElement extends TwoPoint5DElement {
5354
@property({attribute: false})
5455
accessor stageRendererCtx: IStageRenderer | undefined;
5556

56-
readonly #stageRenderer: SignalFuncs<IStageRenderer | undefined> = createSignal();
57-
58-
get stageRenderer(): IStageRenderer | undefined {
59-
return value(this.#stageRenderer[0]);
60-
}
61-
62-
get stageRenderer$(): SignalReader<IStageRenderer | undefined> {
63-
return this.#stageRenderer[0];
64-
}
65-
66-
set stageRenderer(value: IStageRenderer | undefined) {
67-
this.#stageRenderer[1](value);
68-
}
57+
@signal({readAsValue: true}) accessor stageRenderer: IStageRenderer | undefined;
58+
@signalReader() accessor stageRenderer$: SignalReader<IStageRenderer | undefined>;
6959

7060
@property({type: String, reflect: true})
7161
accessor fit: 'contain' | 'cover' | 'fill' | undefined;
@@ -103,26 +93,17 @@ export class Stage2DElement extends TwoPoint5DElement {
10393
@property({type: String, reflect: true, attribute: 'projection-type'})
10494
accessor projectionType: 'parallax' | 'ortho' | 'orthographic' | undefined;
10595

96+
// TODO add autoClear property
97+
10698
readonly #projSignals: SignalMap;
10799
readonly #viewSpecsSignals: SignalMap;
108100

109101
private getViewSpecs(): ParallaxProjectionSpecs | OrthographicProjectionSpecs {
110102
return this.#viewSpecsSignals.getValueObject() as ParallaxProjectionSpecs | OrthographicProjectionSpecs;
111103
}
112104

113-
readonly #projection = createSignal<IProjection | undefined>();
114-
115-
get projection(): IProjection | undefined {
116-
return value(this.#projection[0]);
117-
}
118-
119-
get projection$(): SignalReader<IProjection | undefined> {
120-
return this.#projection[0];
121-
}
122-
123-
set projection(value: IProjection | undefined) {
124-
this.#projection[1](value);
125-
}
105+
@signal({readAsValue: true}) accessor projection: IProjection | undefined;
106+
@signalReader() accessor projection$: SignalReader<IProjection | undefined>;
126107

127108
readonly stage2d = new Stage2D();
128109

packages/twopoint5d/src/events.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,8 @@ export interface StageRenderFrameProps {
5252
/**
5353
* You do not need to call this callback yourself. It's normally done after the event.
5454
* However, you can use this callback to control when the THREE.WebGLRenderer is called.
55-
*
56-
* This is how the stage is rendered: `renderer.render(stage.scene, stage.camera)`
57-
*
58-
* If you provide a `renderHook`, then calling renderFrame will not render anything, that's up to you.
59-
* In this case, this method will just inform the stage that you have rendered this scene, and the stage should not do it.
6055
*/
61-
renderFrame: (renderHook?: () => void) => void;
56+
renderFrame: () => void;
6257
}
6358

6459
export interface Stage2DRenderFrameProps extends StageRenderFrameProps {
+4-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import type {WebGLRenderer} from 'three';
1+
import type {Camera, Scene, WebGLRenderer} from 'three';
2+
3+
export type RenderCmdFunc = (scene: Scene, camera: Camera) => void;
24

35
export interface IStage {
46
resize(width: number, height: number): void;
5-
renderFrame(renderer: WebGLRenderer, now: number, deltaTime: number, frameNo: number): void;
7+
renderFrame(renderer: WebGLRenderer, now: number, deltaTime: number, frameNo: number, renderCmd?: RenderCmdFunc): void;
68
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import type {WebGLRenderer} from 'three';
2+
import {EffectComposer} from 'three/addons/postprocessing/EffectComposer.js';
3+
import {RenderPass} from 'three/addons/postprocessing/RenderPass.js';
4+
import {StageAdded, StageRemoved, type StageAddedProps, type StageRemovedProps} from '../events.js';
5+
import type {IStage} from './IStage.js';
6+
import {Stage2D} from './Stage2D.js';
7+
import {StageRenderer} from './StageRenderer.js';
8+
9+
const isRenderPassable = (stage: IStage): stage is Stage2D => stage instanceof Stage2D;
10+
11+
export class PostEffectsRenderer extends StageRenderer {
12+
composer?: EffectComposer;
13+
renderPass: WeakMap<IStage, RenderPass> = new WeakMap();
14+
15+
constructor() {
16+
super();
17+
this.on([StageAdded, StageRemoved], this);
18+
}
19+
20+
override renderFrame(renderer: WebGLRenderer, now: number, deltaTime: number, frameNo: number): void {
21+
if (!this.composer) {
22+
this.composer = new EffectComposer(renderer);
23+
}
24+
25+
this.stages.forEach((stage) => {
26+
this.resizeStage(stage, this.width, this.height);
27+
stage.stage.renderFrame(renderer, now, deltaTime, frameNo, (scene, camera) => {
28+
const renderPass = this.renderPass.get(stage.stage);
29+
if (renderPass) {
30+
renderPass.clear = (stage.stage as Stage2D).autoClear;
31+
renderPass.scene = scene;
32+
renderPass.camera = camera;
33+
}
34+
});
35+
});
36+
37+
this.composer.render();
38+
}
39+
40+
stageAdded({stage}: StageAddedProps) {
41+
if (isRenderPassable(stage) && !this.renderPass.has(stage)) {
42+
this.renderPass.set(stage, new RenderPass(stage.scene, stage.camera));
43+
console.log('stageAdded as renderPass', {stage, postEffectsRenderer: this});
44+
}
45+
}
46+
47+
stageRemoved({stage}: StageRemovedProps) {
48+
if (this.renderPass.has(stage)) {
49+
const renderPass = this.renderPass.get(stage)!;
50+
this.renderPass.delete(stage);
51+
renderPass.dispose();
52+
console.log('stageRemoved as renderPass', {stage, postEffectsRenderer: this});
53+
}
54+
}
55+
}

packages/twopoint5d/src/stage/Stage2D.ts

+7-5
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ import {
1212
type StageAfterCameraChangedArgs,
1313
} from '../events.js';
1414
import type {IProjection} from './IProjection.js';
15-
import type {IStage} from './IStage.js';
15+
import type {IStage, RenderCmdFunc} from './IStage.js';
1616

1717
export interface Stage2D extends Eventize {}
1818

19+
// TODO extract base class Stage
20+
1921
/**
2022
* The `Stage2D` is a facade for a `THREE.Scene` with a `THREE.Camera`.
2123
* The camera is managed by means of a *projection* description.
@@ -172,19 +174,19 @@ export class Stage2D implements IStage {
172174
#isFirstFrame = true;
173175
#firstFrameProps?: FirstFrameProps;
174176

175-
renderFrame(renderer: WebGLRenderer, now: number, deltaTime: number, frameNo: number): void {
177+
renderFrame(renderer: WebGLRenderer, now: number, deltaTime: number, frameNo: number, renderCmd?: RenderCmdFunc): void {
176178
const {scene, camera} = this;
177179
if (scene && camera) {
178180
const previousAutoClearValue = renderer.autoClear;
179181
renderer.autoClear = this.autoClear;
180182

181183
let isRendered = false;
182184

183-
const renderFrame = (renderHook?: () => void) => {
185+
const renderFrame = () => {
184186
if (!isRendered) {
185187
isRendered = true;
186-
if (renderHook) {
187-
renderHook();
188+
if (renderCmd) {
189+
renderCmd(scene, camera);
188190
} else {
189191
renderer.render(scene, camera);
190192
}

packages/twopoint5d/src/stage/StageRenderer.ts

+8-8
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ export class StageRenderer implements IStageRenderer {
7070
);
7171
}
7272

73-
readonly #stages: StageItem[] = [];
73+
protected readonly stages: StageItem[] = [];
7474

7575
constructor() {
7676
eventize(this);
@@ -88,10 +88,10 @@ export class StageRenderer implements IStageRenderer {
8888
this.width = width;
8989
this.height = height;
9090

91-
this.#stages.forEach((stage) => this.#resizeStage(stage, width, height));
91+
this.stages.forEach((stage) => this.resizeStage(stage, width, height));
9292
}
9393

94-
#resizeStage(stage: StageItem, width: number, height: number): void {
94+
protected resizeStage(stage: StageItem, width: number, height: number): void {
9595
if (stage.width !== width || stage.height !== height) {
9696
stage.width = width;
9797
stage.height = height;
@@ -100,14 +100,14 @@ export class StageRenderer implements IStageRenderer {
100100
}
101101

102102
renderFrame(renderer: WebGLRenderer, now: number, deltaTime: number, frameNo: number): void {
103-
this.#stages.forEach((stage) => {
104-
this.#resizeStage(stage, this.width, this.height);
103+
this.stages.forEach((stage) => {
104+
this.resizeStage(stage, this.width, this.height);
105105
stage.stage.renderFrame(renderer, now, deltaTime, frameNo);
106106
});
107107
}
108108

109109
#getIndex(stage: StageType): number {
110-
return this.#stages.findIndex((item) => item.stage === stage);
110+
return this.stages.findIndex((item) => item.stage === stage);
111111
}
112112

113113
hasStage(stage: StageType): boolean {
@@ -116,7 +116,7 @@ export class StageRenderer implements IStageRenderer {
116116

117117
addStage(stage: StageType): void {
118118
if (!this.hasStage(stage)) {
119-
this.#stages.push({
119+
this.stages.push({
120120
stage,
121121
width: 0,
122122
height: 0,
@@ -128,7 +128,7 @@ export class StageRenderer implements IStageRenderer {
128128
removeStage(stage: StageType): void {
129129
const index = this.#getIndex(stage);
130130
if (index !== -1) {
131-
this.#stages.splice(index, 1);
131+
this.stages.splice(index, 1);
132132
this.emit(StageRemoved, {stage, renderer: this} as StageRemovedProps);
133133
}
134134
}

packages/twopoint5d/src/stage/public-api.ts

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export type * from './IStage.js';
44
export type * from './IStageRenderer.js';
55
export * from './OrthographicProjection.js';
66
export * from './ParallaxProjection.js';
7+
export * from './PostEffectsRenderer.js';
78
export * from './ProjectionPlane.js';
89
export * from './Stage2D.js';
910
export * from './StageRenderer.js';

0 commit comments

Comments
 (0)