@react-three/postprocessing 3.0.4 → 3.1.0
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 +80 -81
- package/dist/EffectComposer.d.ts +14 -6
- package/dist/EffectGroup.d.ts +9 -0
- package/dist/Selection.d.ts +8 -8
- package/dist/createEffectComponent.d.ts +30 -0
- package/dist/effects/ASCII.d.ts +29 -7
- package/dist/effects/Autofocus.d.ts +8 -7
- package/dist/effects/Bloom.d.ts +10 -5
- package/dist/effects/BrightnessContrast.d.ts +15 -5
- package/dist/effects/ChromaticAberration.d.ts +30 -7
- package/dist/effects/ColorAverage.d.ts +7 -8
- package/dist/effects/ColorDepth.d.ts +8 -5
- package/dist/effects/Depth.d.ts +13 -5
- package/dist/effects/DepthOfField.d.ts +7 -18
- package/dist/effects/DotScreen.d.ts +15 -5
- package/dist/effects/FXAA.d.ts +11 -5
- package/dist/effects/Glitch.d.ts +17 -29
- package/dist/effects/GodRays.d.ts +7 -19
- package/dist/effects/Grid.d.ts +8 -8
- package/dist/effects/HueSaturation.d.ts +15 -5
- package/dist/effects/LUT.d.ts +4 -3
- package/dist/effects/LensFlare.d.ts +52 -11
- package/dist/effects/Noise.d.ts +8 -5
- package/dist/effects/Outline.d.ts +9 -25
- package/dist/effects/Pixelation.d.ts +3 -2
- package/dist/effects/Ramp.d.ts +58 -10
- package/dist/effects/SMAA.d.ts +10 -5
- package/dist/effects/SSAO.d.ts +7 -30
- package/dist/effects/ScanlineEffect.d.ts +13 -5
- package/dist/effects/SelectiveBloom.d.ts +5 -10
- package/dist/effects/Sepia.d.ts +13 -5
- package/dist/effects/ShockWave.d.ts +12 -5
- package/dist/effects/Texture.d.ts +7 -8
- package/dist/effects/TiltShift.d.ts +10 -5
- package/dist/effects/TiltShift2.d.ts +44 -8
- package/dist/effects/ToneMapping.d.ts +9 -7
- package/dist/effects/Vignette.d.ts +19 -5
- package/dist/effects/Water.d.ts +17 -5
- package/dist/index.d.ts +15 -11
- package/dist/index.js +1841 -45
- package/dist/index.js.map +1 -1
- package/dist/passes/DepthPicking.d.ts +11 -0
- package/dist/{effects → passes}/N8AO.d.ts +5 -2
- package/dist/util.d.ts +19 -17
- package/dist/wrapEffect.d.ts +12 -0
- package/package.json +74 -67
- package/src/EffectComposer.tsx +379 -200
- package/src/EffectGroup.tsx +74 -0
- package/src/Selection.tsx +86 -46
- package/src/createEffectComponent.tsx +71 -0
- package/src/effects/ASCII.tsx +183 -135
- package/src/effects/Autofocus.tsx +122 -153
- package/src/effects/Bloom.tsx +34 -6
- package/src/effects/BrightnessContrast.tsx +7 -4
- package/src/effects/ChromaticAberration.tsx +22 -5
- package/src/effects/ColorAverage.tsx +4 -15
- package/src/effects/ColorDepth.tsx +25 -4
- package/src/effects/Depth.tsx +6 -4
- package/src/effects/DepthOfField.tsx +109 -89
- package/src/effects/DotScreen.tsx +7 -4
- package/src/effects/FXAA.tsx +6 -4
- package/src/effects/Glitch.tsx +32 -40
- package/src/effects/GodRays.tsx +104 -16
- package/src/effects/Grid.tsx +27 -21
- package/src/effects/HueSaturation.tsx +7 -4
- package/src/effects/LUT.tsx +25 -26
- package/src/effects/LensFlare.tsx +765 -611
- package/src/effects/Noise.tsx +16 -4
- package/src/effects/Outline.tsx +84 -117
- package/src/effects/Pixelation.tsx +20 -15
- package/src/effects/Ramp.tsx +239 -150
- package/src/effects/SMAA.tsx +20 -4
- package/src/effects/SSAO.tsx +152 -41
- package/src/effects/ScanlineEffect.tsx +7 -7
- package/src/effects/SelectiveBloom.tsx +98 -126
- package/src/effects/Sepia.tsx +6 -4
- package/src/effects/ShockWave.tsx +32 -4
- package/src/effects/Texture.tsx +28 -23
- package/src/effects/TiltShift.tsx +32 -4
- package/src/effects/TiltShift2.tsx +156 -90
- package/src/effects/ToneMapping.tsx +19 -6
- package/src/effects/Vignette.tsx +7 -4
- package/src/effects/Water.tsx +50 -35
- package/src/index.ts +43 -40
- package/src/passes/DepthPicking.tsx +63 -0
- package/src/passes/N8AO.tsx +105 -0
- package/src/tests/Autofocus.test.tsx +38 -0
- package/src/tests/Bloom.test.tsx +76 -0
- package/src/tests/ChromaticAberration.test.tsx +81 -0
- package/src/tests/ColorDepth.test.tsx +71 -0
- package/src/tests/DepthOfField.test.tsx +130 -0
- package/src/tests/DepthPicking.test.tsx +250 -0
- package/src/tests/EffectComposer.test.tsx +1404 -0
- package/src/tests/EffectGroup.test.tsx +337 -0
- package/src/tests/Glitch.test.tsx +56 -0
- package/src/tests/GodRays.test.tsx +101 -0
- package/src/tests/Grid.test.tsx +34 -0
- package/src/tests/LUT.test.tsx +64 -0
- package/src/tests/N8AO.test.tsx +92 -0
- package/src/tests/Outline.test.tsx +211 -0
- package/src/tests/SSAO.test.tsx +140 -0
- package/src/tests/Selection.test.tsx +324 -0
- package/src/tests/SelectiveBloom.test.tsx +166 -0
- package/src/tests/ShockWave.test.tsx +95 -0
- package/src/tests/TiltShift.test.tsx +76 -0
- package/src/tests/createEffectComponent.test.tsx +286 -0
- package/src/tests/effects.smoke.test.tsx +238 -0
- package/src/tests/test-utils.tsx +96 -0
- package/src/tests/wrapEffect.test.tsx +209 -0
- package/src/util.tsx +221 -55
- package/src/wrapEffect.tsx +123 -0
- package/src/EffectComposer.test.tsx +0 -126
- package/src/effects/N8AO.tsx +0 -81
package/src/Selection.tsx
CHANGED
|
@@ -1,46 +1,86 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
1
|
+
import { type ThreeElements } from '@react-three/fiber'
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
use,
|
|
5
|
+
useEffect,
|
|
6
|
+
useMemo,
|
|
7
|
+
useRef,
|
|
8
|
+
useState,
|
|
9
|
+
type Dispatch,
|
|
10
|
+
type ReactNode,
|
|
11
|
+
type SetStateAction,
|
|
12
|
+
} from 'react'
|
|
13
|
+
import { type Group, type Line, type Mesh, type Object3D, type Points } from 'three'
|
|
14
|
+
|
|
15
|
+
export type Api = {
|
|
16
|
+
selected: Object3D[]
|
|
17
|
+
select: Dispatch<SetStateAction<Object3D[]>>
|
|
18
|
+
enabled: boolean
|
|
19
|
+
}
|
|
20
|
+
export type SelectApi = Omit<ThreeElements['group'], 'ref'> & {
|
|
21
|
+
enabled?: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const selectionContext = /* @__PURE__ */ createContext<Api | null>(null)
|
|
25
|
+
|
|
26
|
+
export function Selection({ children, enabled = true }: { enabled?: boolean; children: ReactNode }) {
|
|
27
|
+
const [selected, select] = useState<Object3D[]>([])
|
|
28
|
+
const value = useMemo(() => ({ selected, select, enabled }), [selected, select, enabled])
|
|
29
|
+
return <selectionContext.Provider value={value}>{children}</selectionContext.Provider>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Covers Mesh/Line/Points subclasses too, unlike `.type`.
|
|
33
|
+
function isSelectable(object: Object3D): boolean {
|
|
34
|
+
const o = object as Partial<Mesh & Line & Points>
|
|
35
|
+
return !!(o.isMesh || o.isLine || o.isPoints)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function Select({ enabled = false, children, ...props }: SelectApi) {
|
|
39
|
+
const group = useRef<Group>(null!)
|
|
40
|
+
// Stable, unlike the context value - avoids retriggering off our own write.
|
|
41
|
+
const select = use(selectionContext)?.select
|
|
42
|
+
const claimed = useRef<Object3D[]>([])
|
|
43
|
+
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (!select) return
|
|
46
|
+
|
|
47
|
+
const current: Object3D[] = []
|
|
48
|
+
if (enabled) {
|
|
49
|
+
group.current.traverse((o) => {
|
|
50
|
+
if (isSelectable(o)) current.push(o)
|
|
51
|
+
})
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const previouslyClaimed = claimed.current
|
|
55
|
+
claimed.current = current
|
|
56
|
+
|
|
57
|
+
select((prev) => {
|
|
58
|
+
const prevSet = new Set(prev)
|
|
59
|
+
const currentSet = new Set(current)
|
|
60
|
+
const toAdd = current.filter((o) => !prevSet.has(o))
|
|
61
|
+
const toRemove = previouslyClaimed.filter((o) => !currentSet.has(o) && prevSet.has(o))
|
|
62
|
+
if (!toAdd.length && !toRemove.length) return prev
|
|
63
|
+
const toRemoveSet = toRemove.length ? new Set(toRemove) : null
|
|
64
|
+
const kept = toRemoveSet ? prev.filter((o) => !toRemoveSet.has(o)) : prev
|
|
65
|
+
return toAdd.length ? [...kept, ...toAdd] : kept
|
|
66
|
+
})
|
|
67
|
+
}, [enabled, children, select])
|
|
68
|
+
|
|
69
|
+
// Separate from the effect above so unmount cleanup doesn't fire on every enabled/children change.
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
return () => {
|
|
72
|
+
if (!select || !claimed.current.length) return
|
|
73
|
+
const stillClaimed = new Set(claimed.current)
|
|
74
|
+
select((prev) => {
|
|
75
|
+
const next = prev.filter((o) => !stillClaimed.has(o))
|
|
76
|
+
return next.length !== prev.length ? next : prev
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
}, [select])
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
<group ref={group} {...props}>
|
|
83
|
+
{children}
|
|
84
|
+
</group>
|
|
85
|
+
)
|
|
86
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { extend, useThree } from '@react-three/fiber'
|
|
2
|
+
import type { BlendFunction, Effect, Pass } from 'postprocessing'
|
|
3
|
+
import type { ExoticComponent, JSX, Ref } from 'react'
|
|
4
|
+
import { useRef } from 'react'
|
|
5
|
+
import { useLiveDefaults, useMergeRefs } from './util'
|
|
6
|
+
|
|
7
|
+
export type EffectConstructor = new (...args: any[]) => Effect | Pass
|
|
8
|
+
|
|
9
|
+
// The effect's own options type, straight off its constructor - postprocessing
|
|
10
|
+
// already types every effect's sole options object precisely (either inline or,
|
|
11
|
+
// like BloomEffect, as a named exported type); this just strips the `| undefined`
|
|
12
|
+
// that comes from the parameter being optional.
|
|
13
|
+
export type EffectOptions<T extends EffectConstructor> = NonNullable<ConstructorParameters<T>[0]>
|
|
14
|
+
|
|
15
|
+
const components = new WeakMap<EffectConstructor, ExoticComponent<any> | string>()
|
|
16
|
+
let i = 0
|
|
17
|
+
|
|
18
|
+
const BLEND_KEYS = ['blendMode-blendFunction', 'blendMode-opacity-value']
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Registers `effect` as a JSX intrinsic once per class and returns a
|
|
22
|
+
* component that renders it. Everything else - construction from `args`,
|
|
23
|
+
* live prop application (with the same Color/Vector coercion and reset-
|
|
24
|
+
* to-default on removal any r3f element gets), disposal - is r3f's own
|
|
25
|
+
* reconciler, same rules as `<mesh>`/`<meshStandardMaterial>`. Only fits
|
|
26
|
+
* effects whose constructor works with zero arguments (`new Effect()`) -
|
|
27
|
+
* r3f's own reset-on-removal falls back to `0` otherwise, which is wrong
|
|
28
|
+
* for anything non-numeric. Effects that require e.g. scene/camera stay
|
|
29
|
+
* hand-rolled (see Outline.tsx, SelectiveBloom.tsx, ShockWave.tsx).
|
|
30
|
+
*
|
|
31
|
+
* `blendFunction`/`opacity` are pierced through to `blendMode-*` - every
|
|
32
|
+
* `Effect` has them on a nested `blendMode`, not on the effect itself, so a
|
|
33
|
+
* plain top-level prop would silently land on a stray, unread property.
|
|
34
|
+
* Applied via useLiveDefaults, not as plain JSX props: BlendMode's own
|
|
35
|
+
* constructor requires `blendFunction` (no default), so its constructor
|
|
36
|
+
* length isn't 0 either, and r3f's native reset-on-removal falls back to
|
|
37
|
+
* `changedProps[prop] = 0` - which is BlendFunction.SKIP, not a merely
|
|
38
|
+
* "wrong" blend function but one that hides the effect entirely.
|
|
39
|
+
*/
|
|
40
|
+
export function createEffectComponent<T extends EffectConstructor, P extends object>(
|
|
41
|
+
effect: T
|
|
42
|
+
): (
|
|
43
|
+
props: P & {
|
|
44
|
+
blendFunction?: BlendFunction
|
|
45
|
+
opacity?: number
|
|
46
|
+
args?: ConstructorParameters<T>
|
|
47
|
+
ref?: Ref<InstanceType<T>>
|
|
48
|
+
}
|
|
49
|
+
) => JSX.Element {
|
|
50
|
+
return function EffectComponent({ blendFunction, opacity, ref, ...props }: any) {
|
|
51
|
+
let Component = components.get(effect)
|
|
52
|
+
|
|
53
|
+
if (!Component) {
|
|
54
|
+
const key = `@react-three/postprocessing/${effect.name}-${i++}`
|
|
55
|
+
extend({ [key]: effect })
|
|
56
|
+
components.set(effect, (Component = key))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const camera = useThree((state) => state.camera)
|
|
60
|
+
const localRef = useRef<InstanceType<T>>(null)
|
|
61
|
+
const setRef = useMergeRefs(localRef, ref)
|
|
62
|
+
|
|
63
|
+
useLiveDefaults(
|
|
64
|
+
localRef,
|
|
65
|
+
{ 'blendMode-blendFunction': blendFunction, 'blendMode-opacity-value': opacity },
|
|
66
|
+
BLEND_KEYS
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return <Component ref={setRef} camera={camera} {...props} />
|
|
70
|
+
}
|
|
71
|
+
}
|
package/src/effects/ASCII.tsx
CHANGED
|
@@ -1,135 +1,183 @@
|
|
|
1
|
-
// From: https://github.com/emilwidlund/ASCII
|
|
2
|
-
// https://twitter.com/emilwidlund/status/1652386482420609024
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
uniform
|
|
11
|
-
uniform float
|
|
12
|
-
uniform
|
|
13
|
-
uniform
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
vec2
|
|
29
|
-
vec2
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
vec2
|
|
40
|
-
vec2
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
asciiCharacter.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return
|
|
134
|
-
}
|
|
135
|
-
|
|
1
|
+
// From: https://github.com/emilwidlund/ASCII
|
|
2
|
+
// https://twitter.com/emilwidlund/status/1652386482420609024
|
|
3
|
+
|
|
4
|
+
import { Effect } from 'postprocessing'
|
|
5
|
+
import type { Ref } from 'react'
|
|
6
|
+
import { CanvasTexture, Color, type ColorRepresentation, NearestFilter, RepeatWrapping, Texture, Uniform } from 'three'
|
|
7
|
+
import { createEffectComponent } from '../createEffectComponent'
|
|
8
|
+
|
|
9
|
+
const fragment = /* glsl */ `
|
|
10
|
+
uniform sampler2D uCharacters;
|
|
11
|
+
uniform float uCharactersCount;
|
|
12
|
+
uniform float uCellSize;
|
|
13
|
+
uniform bool uInvert;
|
|
14
|
+
uniform vec3 uColor;
|
|
15
|
+
|
|
16
|
+
const vec2 SIZE = vec2(16.);
|
|
17
|
+
|
|
18
|
+
vec3 greyscale(vec3 color, float strength) {
|
|
19
|
+
float g = dot(color, vec3(0.299, 0.587, 0.114));
|
|
20
|
+
return mix(color, vec3(g), strength);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
vec3 greyscale(vec3 color) {
|
|
24
|
+
return greyscale(color, 1.0);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
void mainImage(const in vec4 inputColor, const in vec2 uv, out vec4 outputColor) {
|
|
28
|
+
vec2 cell = resolution / uCellSize;
|
|
29
|
+
vec2 grid = 1.0 / cell;
|
|
30
|
+
vec2 pixelizedUV = grid * (0.5 + floor(uv / grid));
|
|
31
|
+
vec4 pixelized = texture2D(inputBuffer, pixelizedUV);
|
|
32
|
+
float greyscaled = greyscale(pixelized.rgb).r;
|
|
33
|
+
|
|
34
|
+
if (uInvert) {
|
|
35
|
+
greyscaled = 1.0 - greyscaled;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
float characterIndex = floor((uCharactersCount - 1.0) * greyscaled);
|
|
39
|
+
vec2 characterPosition = vec2(mod(characterIndex, SIZE.x), floor(characterIndex / SIZE.y));
|
|
40
|
+
vec2 offset = vec2(characterPosition.x, -characterPosition.y) / SIZE;
|
|
41
|
+
vec2 charUV = mod(uv * (cell / SIZE), 1.0 / SIZE) - vec2(0., 1.0 / SIZE) + offset;
|
|
42
|
+
vec4 asciiCharacter = texture2D(uCharacters, charUV);
|
|
43
|
+
|
|
44
|
+
asciiCharacter.rgb = uColor * asciiCharacter.r;
|
|
45
|
+
asciiCharacter.a = pixelized.a;
|
|
46
|
+
outputColor = asciiCharacter;
|
|
47
|
+
}
|
|
48
|
+
`
|
|
49
|
+
|
|
50
|
+
export type ASCIIProps = {
|
|
51
|
+
font?: string
|
|
52
|
+
characters?: string
|
|
53
|
+
fontSize?: number
|
|
54
|
+
cellSize?: number
|
|
55
|
+
color?: ColorRepresentation
|
|
56
|
+
invert?: boolean
|
|
57
|
+
ref?: Ref<ASCIIEffect>
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
class ASCIIEffect extends Effect {
|
|
61
|
+
private _font: string
|
|
62
|
+
private _characters: string
|
|
63
|
+
private _fontSize: number
|
|
64
|
+
|
|
65
|
+
constructor({
|
|
66
|
+
font = 'arial',
|
|
67
|
+
characters = ` .:,'-^=*+?!|0#X%WM@`,
|
|
68
|
+
fontSize = 54,
|
|
69
|
+
cellSize = 16,
|
|
70
|
+
color = '#ffffff',
|
|
71
|
+
invert = false,
|
|
72
|
+
}: Omit<ASCIIProps, 'ref'> = {}) {
|
|
73
|
+
const uniforms = new Map<string, Uniform>([
|
|
74
|
+
['uCharacters', new Uniform(new Texture())],
|
|
75
|
+
['uCellSize', new Uniform(cellSize)],
|
|
76
|
+
['uCharactersCount', new Uniform(characters.length)],
|
|
77
|
+
['uColor', new Uniform(new Color(color))],
|
|
78
|
+
['uInvert', new Uniform(invert)],
|
|
79
|
+
])
|
|
80
|
+
|
|
81
|
+
super('ASCIIEffect', fragment, { uniforms })
|
|
82
|
+
|
|
83
|
+
this._font = font
|
|
84
|
+
this._characters = characters
|
|
85
|
+
this._fontSize = fontSize
|
|
86
|
+
this.updateCharactersTexture()
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
get cellSize(): number {
|
|
90
|
+
return this.uniforms.get('uCellSize')!.value
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
set cellSize(value: number) {
|
|
94
|
+
this.uniforms.get('uCellSize')!.value = value
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
get invert(): boolean {
|
|
98
|
+
return this.uniforms.get('uInvert')!.value
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
set invert(value: boolean) {
|
|
102
|
+
this.uniforms.get('uInvert')!.value = value
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
get color(): Color {
|
|
106
|
+
return this.uniforms.get('uColor')!.value
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
set color(value: ColorRepresentation) {
|
|
110
|
+
this.uniforms.get('uColor')!.value.set(value)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
get font(): string {
|
|
114
|
+
return this._font
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
set font(value: string) {
|
|
118
|
+
this._font = value
|
|
119
|
+
this.updateCharactersTexture()
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
get characters(): string {
|
|
123
|
+
return this._characters
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
set characters(value: string) {
|
|
127
|
+
this._characters = value
|
|
128
|
+
this.uniforms.get('uCharactersCount')!.value = value.length
|
|
129
|
+
this.updateCharactersTexture()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
get fontSize(): number {
|
|
133
|
+
return this._fontSize
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
set fontSize(value: number) {
|
|
137
|
+
this._fontSize = value
|
|
138
|
+
this.updateCharactersTexture()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Regenerates the character atlas texture - characters/font/fontSize have
|
|
142
|
+
// no cheaper live update path, unlike the plain-uniform props above.
|
|
143
|
+
private updateCharactersTexture(): void {
|
|
144
|
+
const uniform = this.uniforms.get('uCharacters')!
|
|
145
|
+
const previous = uniform.value as Texture
|
|
146
|
+
uniform.value = this.createCharactersTexture(this._characters, this._font, this._fontSize)
|
|
147
|
+
previous.dispose()
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** Draws the characters on a Canvas and returns a texture */
|
|
151
|
+
public createCharactersTexture(characters: string, font: string, fontSize: number): Texture {
|
|
152
|
+
const canvas = document.createElement('canvas')
|
|
153
|
+
const SIZE = 1024
|
|
154
|
+
const MAX_PER_ROW = 16
|
|
155
|
+
const CELL = SIZE / MAX_PER_ROW
|
|
156
|
+
|
|
157
|
+
canvas.width = canvas.height = SIZE
|
|
158
|
+
const texture = new CanvasTexture(canvas, undefined, RepeatWrapping, RepeatWrapping, NearestFilter, NearestFilter)
|
|
159
|
+
const context = canvas.getContext('2d')
|
|
160
|
+
|
|
161
|
+
if (!context) {
|
|
162
|
+
throw new Error('Context not available')
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
context.clearRect(0, 0, SIZE, SIZE)
|
|
166
|
+
context.font = `${fontSize}px ${font}`
|
|
167
|
+
context.textAlign = 'center'
|
|
168
|
+
context.textBaseline = 'middle'
|
|
169
|
+
context.fillStyle = '#fff'
|
|
170
|
+
|
|
171
|
+
for (let i = 0; i < characters.length; i++) {
|
|
172
|
+
const char = characters[i]
|
|
173
|
+
const x = i % MAX_PER_ROW
|
|
174
|
+
const y = Math.floor(i / MAX_PER_ROW)
|
|
175
|
+
context.fillText(char, x * CELL + CELL / 2, y * CELL + CELL / 2)
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
texture.needsUpdate = true
|
|
179
|
+
return texture
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export const ASCII = /* @__PURE__ */ createEffectComponent<typeof ASCIIEffect, ASCIIProps>(ASCIIEffect)
|