@react-three/postprocessing 3.0.4 → 3.0.5
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.
- package/LICENSE +21 -21
- package/README.md +81 -81
- package/dist/EffectComposer.d.ts +7 -6
- package/dist/Selection.d.ts +8 -8
- package/dist/effects/ASCII.d.ts +5 -4
- package/dist/effects/Autofocus.d.ts +8 -7
- package/dist/effects/Bloom.d.ts +2 -5
- package/dist/effects/BrightnessContrast.d.ts +2 -5
- package/dist/effects/ChromaticAberration.d.ts +7 -7
- package/dist/effects/ColorAverage.d.ts +7 -8
- package/dist/effects/ColorDepth.d.ts +2 -5
- package/dist/effects/Depth.d.ts +2 -5
- package/dist/effects/DepthOfField.d.ts +7 -18
- package/dist/effects/DotScreen.d.ts +2 -5
- package/dist/effects/FXAA.d.ts +2 -5
- package/dist/effects/Glitch.d.ts +4 -21
- package/dist/effects/GodRays.d.ts +7 -19
- package/dist/effects/Grid.d.ts +6 -7
- package/dist/effects/HueSaturation.d.ts +2 -5
- package/dist/effects/LUT.d.ts +4 -3
- package/dist/effects/LensFlare.d.ts +8 -8
- package/dist/effects/N8AO.d.ts +4 -2
- package/dist/effects/Noise.d.ts +2 -5
- package/dist/effects/Outline.d.ts +4 -23
- package/dist/effects/Pixelation.d.ts +3 -2
- package/dist/effects/Ramp.d.ts +1 -5
- package/dist/effects/SMAA.d.ts +2 -5
- package/dist/effects/SSAO.d.ts +7 -30
- package/dist/effects/ScanlineEffect.d.ts +2 -5
- package/dist/effects/SelectiveBloom.d.ts +5 -10
- package/dist/effects/Sepia.d.ts +2 -5
- package/dist/effects/ShockWave.d.ts +2 -5
- package/dist/effects/Texture.d.ts +7 -8
- package/dist/effects/TiltShift.d.ts +2 -5
- package/dist/effects/TiltShift2.d.ts +1 -5
- package/dist/effects/ToneMapping.d.ts +2 -6
- package/dist/effects/Vignette.d.ts +2 -5
- package/dist/effects/Water.d.ts +1 -5
- package/dist/index.d.ts +11 -10
- package/dist/index.js +855 -551
- package/dist/index.js.map +1 -1
- package/dist/tests/test-utils.d.ts +12 -0
- package/dist/util.d.ts +25 -17
- package/dist/wrapEffect.d.ts +12 -0
- package/package.json +73 -67
- package/src/EffectComposer.tsx +278 -200
- package/src/Selection.tsx +86 -46
- package/src/effects/ASCII.tsx +136 -135
- package/src/effects/Autofocus.tsx +175 -153
- package/src/effects/Bloom.tsx +6 -6
- package/src/effects/BrightnessContrast.tsx +4 -4
- package/src/effects/ChromaticAberration.tsx +30 -5
- package/src/effects/ColorAverage.tsx +17 -15
- package/src/effects/ColorDepth.tsx +4 -4
- package/src/effects/Depth.tsx +4 -4
- package/src/effects/DepthOfField.tsx +87 -89
- package/src/effects/DotScreen.tsx +4 -4
- package/src/effects/FXAA.tsx +4 -4
- package/src/effects/Glitch.tsx +37 -40
- package/src/effects/GodRays.tsx +20 -16
- package/src/effects/Grid.tsx +28 -21
- package/src/effects/HueSaturation.tsx +4 -4
- package/src/effects/LUT.tsx +27 -26
- package/src/effects/LensFlare.tsx +611 -611
- package/src/effects/N8AO.tsx +81 -81
- package/src/effects/Noise.tsx +4 -4
- package/src/effects/Outline.tsx +85 -117
- package/src/effects/Pixelation.tsx +17 -15
- package/src/effects/Ramp.tsx +150 -150
- package/src/effects/SMAA.tsx +4 -4
- package/src/effects/SSAO.tsx +44 -41
- package/src/effects/ScanlineEffect.tsx +7 -7
- package/src/effects/SelectiveBloom.tsx +116 -126
- package/src/effects/Sepia.tsx +4 -4
- package/src/effects/ShockWave.tsx +4 -4
- package/src/effects/Texture.tsx +27 -23
- package/src/effects/TiltShift.tsx +4 -4
- package/src/effects/TiltShift2.tsx +90 -90
- package/src/effects/ToneMapping.tsx +6 -6
- package/src/effects/Vignette.tsx +4 -4
- package/src/effects/Water.tsx +35 -35
- package/src/index.ts +41 -40
- package/src/tests/ChromaticAberration.test.tsx +31 -0
- package/src/tests/EffectComposer.test.tsx +911 -0
- package/src/tests/Outline.test.tsx +89 -0
- package/src/tests/Selection.test.tsx +324 -0
- package/src/tests/SelectiveBloom.test.tsx +118 -0
- package/src/tests/effects.smoke.test.tsx +234 -0
- package/src/tests/test-utils.tsx +96 -0
- package/src/tests/wrapEffect.test.tsx +209 -0
- package/src/util.tsx +96 -55
- package/src/wrapEffect.tsx +122 -0
- package/src/EffectComposer.test.tsx +0 -126
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { EffectComposer as EffectComposerImpl, OutlineEffect, Selection as PPSelection } from 'postprocessing'
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
import { Mesh, Object3D } from 'three'
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
5
|
+
import { EffectComposer } from '../EffectComposer'
|
|
6
|
+
import { Outline } from '../effects/Outline'
|
|
7
|
+
import { Select, Selection } from '../Selection'
|
|
8
|
+
import { flush, root, waitForComposer } from './test-utils'
|
|
9
|
+
|
|
10
|
+
afterEach(async () => {
|
|
11
|
+
await React.act(async () => {
|
|
12
|
+
root.render(null)
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
describe('Outline', () => {
|
|
17
|
+
it('does not re-set its (empty, declarative-mode) selection on unrelated re-renders', async () => {
|
|
18
|
+
const setSpy = vi.spyOn(PPSelection.prototype, 'set')
|
|
19
|
+
const composerRef = React.createRef<EffectComposerImpl>()
|
|
20
|
+
|
|
21
|
+
const render = (tick: number) =>
|
|
22
|
+
root.render(
|
|
23
|
+
<EffectComposer ref={composerRef}>
|
|
24
|
+
<Outline />
|
|
25
|
+
<group name={`tick-${tick}`} />
|
|
26
|
+
</EffectComposer>
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
await React.act(async () => render(0))
|
|
30
|
+
await waitForComposer(composerRef)
|
|
31
|
+
await flush()
|
|
32
|
+
setSpy.mockClear()
|
|
33
|
+
|
|
34
|
+
for (let t = 1; t <= 5; t++) {
|
|
35
|
+
await React.act(async () => render(t))
|
|
36
|
+
await flush()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
expect(setSpy).not.toHaveBeenCalled()
|
|
40
|
+
setSpy.mockRestore()
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('sets its selection from the Selection/Select API and clears it when the object deselects', async () => {
|
|
44
|
+
const effectRef = React.createRef<OutlineEffect>()
|
|
45
|
+
const meshRef = React.createRef<Mesh>()
|
|
46
|
+
|
|
47
|
+
const render = (enabled: boolean) =>
|
|
48
|
+
root.render(
|
|
49
|
+
<EffectComposer>
|
|
50
|
+
<Selection>
|
|
51
|
+
<Select enabled={enabled}>
|
|
52
|
+
<mesh ref={meshRef}>
|
|
53
|
+
<boxGeometry />
|
|
54
|
+
<meshBasicMaterial />
|
|
55
|
+
</mesh>
|
|
56
|
+
</Select>
|
|
57
|
+
<Outline ref={effectRef} />
|
|
58
|
+
</Selection>
|
|
59
|
+
</EffectComposer>
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
await React.act(async () => render(true))
|
|
63
|
+
await flush()
|
|
64
|
+
await flush()
|
|
65
|
+
|
|
66
|
+
expect(Array.from(effectRef.current!.selection)).toContain(meshRef.current)
|
|
67
|
+
|
|
68
|
+
await React.act(async () => render(false))
|
|
69
|
+
await flush()
|
|
70
|
+
await flush()
|
|
71
|
+
|
|
72
|
+
expect(Array.from(effectRef.current!.selection)).not.toContain(meshRef.current)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('does not throw when a selection ref has not attached yet', async () => {
|
|
76
|
+
const composerRef = React.createRef<EffectComposerImpl>()
|
|
77
|
+
const unattachedRef = React.createRef<Object3D>()
|
|
78
|
+
|
|
79
|
+
await React.act(async () =>
|
|
80
|
+
root.render(
|
|
81
|
+
<EffectComposer ref={composerRef}>
|
|
82
|
+
<Outline selection={[unattachedRef]} />
|
|
83
|
+
</EffectComposer>
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
await waitForComposer(composerRef)
|
|
87
|
+
await expect(flush()).resolves.not.toThrow()
|
|
88
|
+
})
|
|
89
|
+
})
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import * as React from 'react'
|
|
2
|
+
import * as THREE from 'three'
|
|
3
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
4
|
+
import { Select, Selection, selectionContext } from '../Selection'
|
|
5
|
+
import { flush, root } from './test-utils'
|
|
6
|
+
|
|
7
|
+
afterEach(async () => {
|
|
8
|
+
await React.act(async () => {
|
|
9
|
+
root.render(null)
|
|
10
|
+
})
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
function Capture({ onSnapshot }: { onSnapshot: (selected: THREE.Object3D[]) => void }) {
|
|
14
|
+
const api = React.useContext(selectionContext)
|
|
15
|
+
React.useEffect(() => {
|
|
16
|
+
onSnapshot(api?.selected ?? [])
|
|
17
|
+
})
|
|
18
|
+
return null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
describe('Selection/Select', () => {
|
|
22
|
+
it('settles to a stable selected array without looping', async () => {
|
|
23
|
+
const snapshots: THREE.Object3D[][] = []
|
|
24
|
+
const meshRef = React.createRef<THREE.Mesh>()
|
|
25
|
+
|
|
26
|
+
await React.act(async () =>
|
|
27
|
+
root.render(
|
|
28
|
+
<Selection>
|
|
29
|
+
<Capture onSnapshot={(s) => snapshots.push(s)} />
|
|
30
|
+
<Select enabled>
|
|
31
|
+
<mesh ref={meshRef}>
|
|
32
|
+
<boxGeometry />
|
|
33
|
+
<meshBasicMaterial />
|
|
34
|
+
</mesh>
|
|
35
|
+
</Select>
|
|
36
|
+
</Selection>
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
await flush()
|
|
40
|
+
await flush()
|
|
41
|
+
|
|
42
|
+
// Exactly one render for the initial (empty) commit, one for the
|
|
43
|
+
// single necessary addition - the original bug kept re-triggering
|
|
44
|
+
// itself, so this would grow unbounded (and eventually throw) instead
|
|
45
|
+
// of stopping at 2.
|
|
46
|
+
expect(snapshots).toEqual([[], [meshRef.current]])
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('selects a Line and a Points object, not just Mesh', async () => {
|
|
50
|
+
const snapshots: THREE.Object3D[][] = []
|
|
51
|
+
const lineRef = React.createRef<THREE.Line>()
|
|
52
|
+
const pointsRef = React.createRef<THREE.Points>()
|
|
53
|
+
|
|
54
|
+
await React.act(async () =>
|
|
55
|
+
root.render(
|
|
56
|
+
<Selection>
|
|
57
|
+
<Capture onSnapshot={(s) => snapshots.push(s)} />
|
|
58
|
+
<Select enabled>
|
|
59
|
+
<threeLine ref={lineRef}>
|
|
60
|
+
<bufferGeometry />
|
|
61
|
+
<lineBasicMaterial />
|
|
62
|
+
</threeLine>
|
|
63
|
+
</Select>
|
|
64
|
+
<Select enabled>
|
|
65
|
+
<points ref={pointsRef}>
|
|
66
|
+
<bufferGeometry />
|
|
67
|
+
<pointsMaterial />
|
|
68
|
+
</points>
|
|
69
|
+
</Select>
|
|
70
|
+
</Selection>
|
|
71
|
+
)
|
|
72
|
+
)
|
|
73
|
+
await flush()
|
|
74
|
+
await flush()
|
|
75
|
+
|
|
76
|
+
const last = snapshots[snapshots.length - 1]
|
|
77
|
+
expect(last).toContain(lineRef.current)
|
|
78
|
+
expect(last).toContain(pointsRef.current)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('removes its objects once unmounted, leaving a sibling Select untouched', async () => {
|
|
82
|
+
const snapshots: THREE.Object3D[][] = []
|
|
83
|
+
const meshARef = React.createRef<THREE.Mesh>()
|
|
84
|
+
const meshBRef = React.createRef<THREE.Mesh>()
|
|
85
|
+
|
|
86
|
+
const render = (mountA: boolean) =>
|
|
87
|
+
root.render(
|
|
88
|
+
<Selection>
|
|
89
|
+
<Capture onSnapshot={(s) => snapshots.push(s)} />
|
|
90
|
+
{mountA && (
|
|
91
|
+
<Select key="a" enabled>
|
|
92
|
+
<mesh ref={meshARef}>
|
|
93
|
+
<boxGeometry />
|
|
94
|
+
<meshBasicMaterial />
|
|
95
|
+
</mesh>
|
|
96
|
+
</Select>
|
|
97
|
+
)}
|
|
98
|
+
<Select key="b" enabled>
|
|
99
|
+
<mesh ref={meshBRef}>
|
|
100
|
+
<boxGeometry />
|
|
101
|
+
<meshBasicMaterial />
|
|
102
|
+
</mesh>
|
|
103
|
+
</Select>
|
|
104
|
+
</Selection>
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
await React.act(async () => render(true))
|
|
108
|
+
await flush()
|
|
109
|
+
await flush()
|
|
110
|
+
expect(snapshots[snapshots.length - 1]).toEqual(expect.arrayContaining([meshARef.current, meshBRef.current]))
|
|
111
|
+
|
|
112
|
+
await React.act(async () => render(false))
|
|
113
|
+
await flush()
|
|
114
|
+
await flush()
|
|
115
|
+
|
|
116
|
+
const last = snapshots[snapshots.length - 1]
|
|
117
|
+
expect(last).not.toContain(meshARef.current)
|
|
118
|
+
expect(last).toContain(meshBRef.current)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
it('removes its objects when enabled toggles to false', async () => {
|
|
122
|
+
const snapshots: THREE.Object3D[][] = []
|
|
123
|
+
const meshRef = React.createRef<THREE.Mesh>()
|
|
124
|
+
|
|
125
|
+
const render = (enabled: boolean) =>
|
|
126
|
+
root.render(
|
|
127
|
+
<Selection>
|
|
128
|
+
<Capture onSnapshot={(s) => snapshots.push(s)} />
|
|
129
|
+
<Select enabled={enabled}>
|
|
130
|
+
<mesh ref={meshRef}>
|
|
131
|
+
<boxGeometry />
|
|
132
|
+
<meshBasicMaterial />
|
|
133
|
+
</mesh>
|
|
134
|
+
</Select>
|
|
135
|
+
</Selection>
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
await React.act(async () => render(true))
|
|
139
|
+
await flush()
|
|
140
|
+
await flush()
|
|
141
|
+
expect(snapshots[snapshots.length - 1]).toContain(meshRef.current)
|
|
142
|
+
|
|
143
|
+
await React.act(async () => render(false))
|
|
144
|
+
await flush()
|
|
145
|
+
await flush()
|
|
146
|
+
expect(snapshots[snapshots.length - 1]).not.toContain(meshRef.current)
|
|
147
|
+
})
|
|
148
|
+
|
|
149
|
+
it('selects a SkinnedMesh - isMesh is inherited, not just the exact type string', async () => {
|
|
150
|
+
const snapshots: THREE.Object3D[][] = []
|
|
151
|
+
const meshRef = React.createRef<THREE.SkinnedMesh>()
|
|
152
|
+
|
|
153
|
+
await React.act(async () =>
|
|
154
|
+
root.render(
|
|
155
|
+
<Selection>
|
|
156
|
+
<Capture onSnapshot={(s) => snapshots.push(s)} />
|
|
157
|
+
<Select enabled>
|
|
158
|
+
<skinnedMesh ref={meshRef} />
|
|
159
|
+
</Select>
|
|
160
|
+
</Selection>
|
|
161
|
+
)
|
|
162
|
+
)
|
|
163
|
+
await flush()
|
|
164
|
+
await flush()
|
|
165
|
+
|
|
166
|
+
expect(snapshots[snapshots.length - 1]).toContain(meshRef.current)
|
|
167
|
+
})
|
|
168
|
+
|
|
169
|
+
it('does not duplicate an object claimed by nested Selects', async () => {
|
|
170
|
+
const snapshots: THREE.Object3D[][] = []
|
|
171
|
+
const meshRef = React.createRef<THREE.Mesh>()
|
|
172
|
+
|
|
173
|
+
await React.act(async () =>
|
|
174
|
+
root.render(
|
|
175
|
+
<Selection>
|
|
176
|
+
<Capture onSnapshot={(s) => snapshots.push(s)} />
|
|
177
|
+
<Select enabled>
|
|
178
|
+
<Select enabled>
|
|
179
|
+
<mesh ref={meshRef}>
|
|
180
|
+
<boxGeometry />
|
|
181
|
+
<meshBasicMaterial />
|
|
182
|
+
</mesh>
|
|
183
|
+
</Select>
|
|
184
|
+
</Select>
|
|
185
|
+
</Selection>
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
await flush()
|
|
189
|
+
await flush()
|
|
190
|
+
await flush()
|
|
191
|
+
|
|
192
|
+
const last = snapshots[snapshots.length - 1]
|
|
193
|
+
expect(last.filter((o) => o === meshRef.current)).toHaveLength(1)
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
it('keeps an object selected via the outer Select after the inner one disables', async () => {
|
|
197
|
+
const snapshots: THREE.Object3D[][] = []
|
|
198
|
+
const meshRef = React.createRef<THREE.Mesh>()
|
|
199
|
+
|
|
200
|
+
const render = (innerEnabled: boolean) =>
|
|
201
|
+
root.render(
|
|
202
|
+
<Selection>
|
|
203
|
+
<Capture onSnapshot={(s) => snapshots.push(s)} />
|
|
204
|
+
<Select enabled>
|
|
205
|
+
<Select enabled={innerEnabled}>
|
|
206
|
+
<mesh ref={meshRef}>
|
|
207
|
+
<boxGeometry />
|
|
208
|
+
<meshBasicMaterial />
|
|
209
|
+
</mesh>
|
|
210
|
+
</Select>
|
|
211
|
+
</Select>
|
|
212
|
+
</Selection>
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
await React.act(async () => render(true))
|
|
216
|
+
await flush()
|
|
217
|
+
await flush()
|
|
218
|
+
await flush()
|
|
219
|
+
expect(snapshots[snapshots.length - 1]).toContain(meshRef.current)
|
|
220
|
+
|
|
221
|
+
await React.act(async () => render(false))
|
|
222
|
+
await flush()
|
|
223
|
+
await flush()
|
|
224
|
+
await flush()
|
|
225
|
+
|
|
226
|
+
// The inner Select's own cleanup drops its claim, but the mesh never
|
|
227
|
+
// left the outer Select's subtree - its own traversal still finds it.
|
|
228
|
+
expect(snapshots[snapshots.length - 1]).toContain(meshRef.current)
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it('does not change the selected array reference on a re-render where children only changes identity', async () => {
|
|
232
|
+
const refs: THREE.Object3D[][] = []
|
|
233
|
+
const meshRef = React.createRef<THREE.Mesh>()
|
|
234
|
+
|
|
235
|
+
function CaptureRef() {
|
|
236
|
+
const api = React.useContext(selectionContext)
|
|
237
|
+
React.useEffect(() => {
|
|
238
|
+
if (api) refs.push(api.selected)
|
|
239
|
+
})
|
|
240
|
+
return null
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// A fresh JSX tree every call, like a parent re-rendering for an
|
|
244
|
+
// unrelated reason - Select's own `children` prop is a new reference
|
|
245
|
+
// each time even though the underlying mesh never changes.
|
|
246
|
+
const render = () =>
|
|
247
|
+
root.render(
|
|
248
|
+
<Selection>
|
|
249
|
+
<CaptureRef />
|
|
250
|
+
<Select enabled>
|
|
251
|
+
<mesh ref={meshRef}>
|
|
252
|
+
<boxGeometry />
|
|
253
|
+
<meshBasicMaterial />
|
|
254
|
+
</mesh>
|
|
255
|
+
</Select>
|
|
256
|
+
</Selection>
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
await React.act(async () => render())
|
|
260
|
+
await flush()
|
|
261
|
+
await flush()
|
|
262
|
+
|
|
263
|
+
refs.length = 0
|
|
264
|
+
for (let i = 0; i < 5; i++) {
|
|
265
|
+
await React.act(async () => render())
|
|
266
|
+
await flush()
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
expect(new Set(refs).size).toBe(1)
|
|
270
|
+
})
|
|
271
|
+
|
|
272
|
+
it('a cleanup updater returns the same reference when nothing is left for it to remove', async () => {
|
|
273
|
+
const meshRef = React.createRef<THREE.Mesh>()
|
|
274
|
+
const updaters: Array<(prev: THREE.Object3D[]) => THREE.Object3D[]> = []
|
|
275
|
+
|
|
276
|
+
// React batches both nested Selects' cleanup calls into a single
|
|
277
|
+
// re-render regardless of this bail-out (the net change from mount to
|
|
278
|
+
// unmount is real), so the optimization isn't observable through
|
|
279
|
+
// rendering alone. Capture the raw updater functions instead and
|
|
280
|
+
// replay them directly to verify the second one bails.
|
|
281
|
+
function CapturingSelection({ children }: { children: React.ReactNode }) {
|
|
282
|
+
const [selected, setSelected] = React.useState<THREE.Object3D[]>([])
|
|
283
|
+
const select = React.useCallback((updater: React.SetStateAction<THREE.Object3D[]>) => {
|
|
284
|
+
if (typeof updater === 'function') updaters.push(updater as (prev: THREE.Object3D[]) => THREE.Object3D[])
|
|
285
|
+
setSelected(updater)
|
|
286
|
+
}, [])
|
|
287
|
+
const value = React.useMemo(() => ({ selected, select, enabled: true }), [selected, select])
|
|
288
|
+
return <selectionContext.Provider value={value}>{children}</selectionContext.Provider>
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
const render = (mounted: boolean) =>
|
|
292
|
+
root.render(
|
|
293
|
+
<CapturingSelection>
|
|
294
|
+
{mounted && (
|
|
295
|
+
<Select enabled>
|
|
296
|
+
<Select enabled>
|
|
297
|
+
<mesh ref={meshRef}>
|
|
298
|
+
<boxGeometry />
|
|
299
|
+
<meshBasicMaterial />
|
|
300
|
+
</mesh>
|
|
301
|
+
</Select>
|
|
302
|
+
</Select>
|
|
303
|
+
)}
|
|
304
|
+
</CapturingSelection>
|
|
305
|
+
)
|
|
306
|
+
|
|
307
|
+
await React.act(async () => render(true))
|
|
308
|
+
await flush()
|
|
309
|
+
await flush()
|
|
310
|
+
await flush()
|
|
311
|
+
|
|
312
|
+
const mesh = meshRef.current!
|
|
313
|
+
updaters.length = 0
|
|
314
|
+
await React.act(async () => render(false))
|
|
315
|
+
await flush()
|
|
316
|
+
await flush()
|
|
317
|
+
await flush()
|
|
318
|
+
|
|
319
|
+
expect(updaters).toHaveLength(2)
|
|
320
|
+
const afterFirst = updaters[0]([mesh])
|
|
321
|
+
const afterSecond = updaters[1](afterFirst)
|
|
322
|
+
expect(afterSecond).toBe(afterFirst)
|
|
323
|
+
})
|
|
324
|
+
})
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { EffectComposer as EffectComposerImpl, SelectiveBloomEffect } from 'postprocessing'
|
|
2
|
+
import * as React from 'react'
|
|
3
|
+
import { Mesh, Object3D, PointLight } from 'three'
|
|
4
|
+
import { afterEach, describe, expect, it } from 'vitest'
|
|
5
|
+
import { EffectComposer } from '../EffectComposer'
|
|
6
|
+
import { SelectiveBloom } from '../effects/SelectiveBloom'
|
|
7
|
+
import { Select, Selection } from '../Selection'
|
|
8
|
+
import { flush, root, waitForComposer } from './test-utils'
|
|
9
|
+
|
|
10
|
+
afterEach(async () => {
|
|
11
|
+
await React.act(async () => {
|
|
12
|
+
root.render(null)
|
|
13
|
+
})
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
describe('SelectiveBloom', () => {
|
|
17
|
+
it('does not reconstruct the effect (with its GPU resources) on unrelated re-renders', async () => {
|
|
18
|
+
const composerRef = React.createRef<EffectComposerImpl>()
|
|
19
|
+
const effectRef = React.createRef<SelectiveBloomEffect>()
|
|
20
|
+
const light = new PointLight()
|
|
21
|
+
|
|
22
|
+
const render = (tick: number) =>
|
|
23
|
+
root.render(
|
|
24
|
+
<EffectComposer ref={composerRef}>
|
|
25
|
+
<SelectiveBloom ref={effectRef} lights={[light]} intensity={2} />
|
|
26
|
+
<group name={`tick-${tick}`} />
|
|
27
|
+
</EffectComposer>
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
await React.act(async () => render(0))
|
|
31
|
+
await waitForComposer(composerRef)
|
|
32
|
+
await flush()
|
|
33
|
+
const first = effectRef.current
|
|
34
|
+
expect(first).toBeTruthy()
|
|
35
|
+
|
|
36
|
+
for (let t = 1; t <= 5; t++) {
|
|
37
|
+
await React.act(async () => render(t))
|
|
38
|
+
await flush()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
expect(effectRef.current).toBe(first)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('sets its selection from the Selection/Select API and clears it when the object deselects', async () => {
|
|
45
|
+
const effectRef = React.createRef<SelectiveBloomEffect>()
|
|
46
|
+
const meshRef = React.createRef<Mesh>()
|
|
47
|
+
const light = new PointLight()
|
|
48
|
+
|
|
49
|
+
const render = (enabled: boolean) =>
|
|
50
|
+
root.render(
|
|
51
|
+
<EffectComposer>
|
|
52
|
+
<Selection>
|
|
53
|
+
<Select enabled={enabled}>
|
|
54
|
+
<mesh ref={meshRef}>
|
|
55
|
+
<boxGeometry />
|
|
56
|
+
<meshBasicMaterial />
|
|
57
|
+
</mesh>
|
|
58
|
+
</Select>
|
|
59
|
+
<SelectiveBloom ref={effectRef} lights={[light]} />
|
|
60
|
+
</Selection>
|
|
61
|
+
</EffectComposer>
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
await React.act(async () => render(true))
|
|
65
|
+
await flush()
|
|
66
|
+
await flush()
|
|
67
|
+
|
|
68
|
+
expect(Array.from(effectRef.current!.selection)).toContain(meshRef.current)
|
|
69
|
+
|
|
70
|
+
await React.act(async () => render(false))
|
|
71
|
+
await flush()
|
|
72
|
+
await flush()
|
|
73
|
+
|
|
74
|
+
expect(Array.from(effectRef.current!.selection)).not.toContain(meshRef.current)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
it('moves lights to the new render layer when selectionLayer changes', async () => {
|
|
78
|
+
const composerRef = React.createRef<EffectComposerImpl>()
|
|
79
|
+
const effectRef = React.createRef<SelectiveBloomEffect>()
|
|
80
|
+
const light = new PointLight()
|
|
81
|
+
const onLayer = (n: number) => light.layers.test({ mask: 1 << n } as never)
|
|
82
|
+
|
|
83
|
+
const render = (layer: number) =>
|
|
84
|
+
root.render(
|
|
85
|
+
<EffectComposer ref={composerRef}>
|
|
86
|
+
<SelectiveBloom ref={effectRef} lights={[light]} selectionLayer={layer} />
|
|
87
|
+
</EffectComposer>
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
await React.act(async () => render(10))
|
|
91
|
+
await waitForComposer(composerRef)
|
|
92
|
+
await flush()
|
|
93
|
+
await flush()
|
|
94
|
+
expect(onLayer(10)).toBe(true)
|
|
95
|
+
|
|
96
|
+
await React.act(async () => render(15))
|
|
97
|
+
await flush()
|
|
98
|
+
await flush()
|
|
99
|
+
|
|
100
|
+
expect(onLayer(15)).toBe(true)
|
|
101
|
+
expect(onLayer(10)).toBe(false)
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
it('does not throw when a lights ref has not attached yet', async () => {
|
|
105
|
+
const composerRef = React.createRef<EffectComposerImpl>()
|
|
106
|
+
const unattachedRef = React.createRef<Object3D>()
|
|
107
|
+
|
|
108
|
+
await React.act(async () =>
|
|
109
|
+
root.render(
|
|
110
|
+
<EffectComposer ref={composerRef}>
|
|
111
|
+
<SelectiveBloom lights={[unattachedRef]} />
|
|
112
|
+
</EffectComposer>
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
await waitForComposer(composerRef)
|
|
116
|
+
await expect(flush()).resolves.not.toThrow()
|
|
117
|
+
})
|
|
118
|
+
})
|