@takram/three-clouds 0.2.2 → 0.4.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/README.md +25 -17
  3. package/build/index.cjs +9 -9
  4. package/build/index.cjs.map +1 -1
  5. package/build/index.js +70 -77
  6. package/build/index.js.map +1 -1
  7. package/build/r3f.cjs +1 -1
  8. package/build/r3f.cjs.map +1 -1
  9. package/build/r3f.js +180 -186
  10. package/build/r3f.js.map +1 -1
  11. package/build/shared.cjs +102 -74
  12. package/build/shared.cjs.map +1 -1
  13. package/build/shared.js +443 -380
  14. package/build/shared.js.map +1 -1
  15. package/package.json +3 -3
  16. package/src/CloudLayers.ts +2 -2
  17. package/src/CloudsEffect.ts +52 -29
  18. package/src/CloudsMaterial.ts +53 -30
  19. package/src/CloudsPass.ts +5 -6
  20. package/src/PassBase.ts +1 -1
  21. package/src/Procedural3DTexture.ts +2 -4
  22. package/src/ProceduralTexture.ts +1 -3
  23. package/src/ShaderArrayPass.ts +5 -5
  24. package/src/ShadowMaterial.ts +4 -4
  25. package/src/ShadowPass.ts +5 -6
  26. package/src/constants.ts +1 -0
  27. package/src/helpers/setArrayRenderTargetLayers.ts +3 -6
  28. package/src/qualityPresets.ts +5 -5
  29. package/src/r3f/CloudLayer.tsx +65 -63
  30. package/src/r3f/Clouds.tsx +170 -188
  31. package/src/shaders/clouds.frag +64 -50
  32. package/src/shaders/clouds.glsl +1 -1
  33. package/src/shaders/clouds.vert +18 -5
  34. package/src/shaders/parameters.glsl +3 -2
  35. package/src/shaders/shadowResolve.frag +2 -2
  36. package/src/shaders/types.glsl +1 -1
  37. package/src/shaders/varianceClipping.glsl +2 -2
  38. package/src/uniforms.ts +3 -3
  39. package/types/CloudsEffect.d.ts +19 -9
  40. package/types/CloudsMaterial.d.ts +10 -5
  41. package/types/qualityPresets.d.ts +1 -1
  42. package/types/r3f/CloudLayer.d.ts +3 -1
  43. package/types/r3f/Clouds.d.ts +2 -1
@@ -1,12 +1,13 @@
1
1
  import {
2
- forwardRef,
3
2
  useContext,
4
3
  useEffect,
5
4
  useLayoutEffect,
6
- useState
5
+ useState,
6
+ type FC,
7
+ type Ref
7
8
  } from 'react'
8
9
 
9
- import { type ExpandNestedProps } from '@takram/three-geospatial/r3f'
10
+ import type { ExpandNestedProps } from '@takram/three-geospatial/r3f'
10
11
 
11
12
  import {
12
13
  CloudLayer as CloudLayerImpl,
@@ -18,78 +19,79 @@ import { CloudLayersContext } from './CloudLayers'
18
19
  export interface CloudLayerProps
19
20
  extends CloudLayerLike,
20
21
  ExpandNestedProps<CloudLayerLike, 'densityProfile'> {
22
+ ref?: Ref<CloudLayerImpl>
21
23
  index?: number
22
24
  }
23
25
 
24
- export const CloudLayer = forwardRef<CloudLayerImpl, CloudLayerProps>(
25
- function CloudLayer({ index: indexProp, ...props }, forwardedRef) {
26
- const context = useContext(CloudLayersContext)
27
- if (context == null) {
28
- throw new Error(
29
- 'CloudLayer can only be used within the Clouds component!'
30
- )
31
- }
26
+ export const CloudLayer: FC<CloudLayerProps> = ({
27
+ ref: forwardedRef,
28
+ index: indexProp,
29
+ ...props
30
+ }) => {
31
+ const context = useContext(CloudLayersContext)
32
+ if (context == null) {
33
+ throw new Error('CloudLayer can only be used within the Clouds component!')
34
+ }
32
35
 
33
- const { layers, indexPool, disableDefault } = context
34
- const [index, setIndex] = useState<number>()
36
+ const { layers, indexPool, disableDefault } = context
37
+ const [index, setIndex] = useState<number>()
35
38
 
36
- useLayoutEffect(() => {
37
- if (indexProp != null) {
38
- const poolIndex = indexPool.indexOf(indexProp)
39
- if (poolIndex !== -1) {
40
- indexPool.splice(poolIndex, 1)
41
- setIndex(indexProp)
42
- return () => {
43
- indexPool.push(indexProp)
44
- setIndex(undefined)
45
- }
39
+ useLayoutEffect(() => {
40
+ if (indexProp != null) {
41
+ const poolIndex = indexPool.indexOf(indexProp)
42
+ if (poolIndex !== -1) {
43
+ indexPool.splice(poolIndex, 1)
44
+ setIndex(indexProp)
45
+ return () => {
46
+ indexPool.push(indexProp)
47
+ setIndex(undefined)
46
48
  }
47
- } else {
48
- // Sorting is just for predictability. Layer order is still not defined,
49
- // but it doesn't matter.
50
- const index = indexPool.sort((a, b) => a - b).shift()
51
- if (index != null) {
52
- setIndex(index)
53
- return () => {
54
- indexPool.push(index)
55
- setIndex(undefined)
56
- }
57
- }
58
- }
59
- }, [indexProp, layers, indexPool])
60
-
61
- useLayoutEffect(() => {
62
- if (index == null) {
63
- return
64
- }
65
- const layer = layers[index]
66
- return () => {
67
- layer.copy(
68
- disableDefault ? CloudLayerImpl.DEFAULT : CloudLayers.DEFAULT[index]
69
- )
70
- }
71
- }, [layers, index, disableDefault])
72
-
73
- useEffect(() => {
74
- if (index == null) {
75
- return
76
49
  }
77
- if (typeof forwardedRef === 'function') {
78
- forwardedRef(layers[index])
79
- } else if (forwardedRef != null) {
80
- forwardedRef.current = layers[index]
50
+ } else {
51
+ // Sorting is just for predictability. Layer order is still not defined,
52
+ // but it doesn't matter.
53
+ const index = indexPool.sort((a, b) => a - b).shift()
54
+ if (index != null) {
55
+ setIndex(index)
56
+ return () => {
57
+ indexPool.push(index)
58
+ setIndex(undefined)
59
+ }
81
60
  }
82
- }, [forwardedRef, layers, index])
61
+ }
62
+ }, [indexProp, layers, indexPool])
83
63
 
84
- // Surely this resets any modifications made via forwarded ref.
85
- if (index != null) {
86
- const layer = layers[index]
64
+ useLayoutEffect(() => {
65
+ if (index == null) {
66
+ return
67
+ }
68
+ const layer = layers[index]
69
+ return () => {
87
70
  layer.copy(
88
71
  disableDefault ? CloudLayerImpl.DEFAULT : CloudLayers.DEFAULT[index]
89
72
  )
90
- layer.set(props)
91
73
  }
74
+ }, [layers, index, disableDefault])
75
+
76
+ useEffect(() => {
77
+ if (index == null) {
78
+ return
79
+ }
80
+ if (typeof forwardedRef === 'function') {
81
+ forwardedRef(layers[index])
82
+ } else if (forwardedRef != null) {
83
+ forwardedRef.current = layers[index]
84
+ }
85
+ }, [forwardedRef, layers, index])
92
86
 
93
- return null
87
+ // Surely this resets any modifications made via forwarded ref.
88
+ if (index != null) {
89
+ const layer = layers[index]
90
+ layer.copy(
91
+ disableDefault ? CloudLayerImpl.DEFAULT : CloudLayers.DEFAULT[index]
92
+ )
93
+ layer.set(props)
94
94
  }
95
- )
95
+
96
+ return null
97
+ }
@@ -1,33 +1,26 @@
1
1
  import { useFrame, useThree, type ElementProps } from '@react-three/fiber'
2
2
  import { EffectComposerContext } from '@react-three/postprocessing'
3
+ import { useCallback, useContext, useEffect, useMemo, type FC } from 'react'
3
4
  import {
4
- forwardRef,
5
- useCallback,
6
- useContext,
7
- useEffect,
8
- useMemo,
9
- useState
10
- } from 'react'
11
- import {
5
+ Data3DTexture,
12
6
  LinearFilter,
13
7
  LinearMipMapLinearFilter,
14
8
  NoColorSpace,
15
9
  RedFormat,
16
10
  RepeatWrapping,
17
11
  TextureLoader,
18
- type Data3DTexture,
19
12
  type Texture,
20
13
  type WebGLRenderer
21
14
  } from 'three'
22
15
 
23
16
  import { AtmosphereContext, separateProps } from '@takram/three-atmosphere/r3f'
24
17
  import {
25
- createData3DTextureLoaderClass,
18
+ DataTextureLoader,
26
19
  DEFAULT_STBN_URL,
27
20
  parseUint8Array,
28
21
  STBNLoader
29
22
  } from '@takram/three-geospatial'
30
- import { type ExpandNestedProps } from '@takram/three-geospatial/r3f'
23
+ import type { ExpandNestedProps } from '@takram/three-geospatial/r3f'
31
24
 
32
25
  import {
33
26
  CloudsEffect,
@@ -42,94 +35,86 @@ import {
42
35
  DEFAULT_SHAPE_URL,
43
36
  DEFAULT_TURBULENCE_URL
44
37
  } from '../constants'
45
- import { type Procedural3DTexture } from '../Procedural3DTexture'
46
- import { type ProceduralTexture } from '../ProceduralTexture'
38
+ import type { Procedural3DTexture } from '../Procedural3DTexture'
39
+ import type { ProceduralTexture } from '../ProceduralTexture'
47
40
  import { CloudLayers } from './CloudLayers'
48
41
 
49
- function useTextureState(
42
+ function useLoadTexture(
50
43
  input: string | Texture | ProceduralTexture,
51
44
  gl: WebGLRenderer
52
45
  ): Texture | ProceduralTexture | null {
53
- const [data, setData] = useState(typeof input !== 'string' ? input : null)
46
+ const loadedTexture = useMemo(
47
+ () =>
48
+ typeof input === 'string'
49
+ ? new TextureLoader().load(input, texture => {
50
+ texture.minFilter = LinearMipMapLinearFilter
51
+ texture.magFilter = LinearFilter
52
+ texture.wrapS = RepeatWrapping
53
+ texture.wrapT = RepeatWrapping
54
+ texture.colorSpace = NoColorSpace
55
+ texture.needsUpdate = true
56
+ })
57
+ : undefined,
58
+ [input]
59
+ )
54
60
  useEffect(() => {
55
- if (typeof input === 'string') {
56
- const loader = new TextureLoader()
57
- ;(async () => {
58
- const texture = await loader.loadAsync(input)
59
- texture.minFilter = LinearMipMapLinearFilter
60
- texture.magFilter = LinearFilter
61
- texture.wrapS = RepeatWrapping
62
- texture.wrapT = RepeatWrapping
63
- texture.colorSpace = NoColorSpace
64
- texture.needsUpdate = true
65
-
66
- // WORKAROUND: The color space resets to sRGB for unknown reason, unless
67
- // the texture is initialized here.
68
- gl.initTexture(texture)
69
-
70
- setData(texture)
71
- })().catch(error => {
72
- console.error(error)
73
- })
74
- } else {
75
- setData(input)
61
+ if (loadedTexture != null) {
62
+ return () => {
63
+ loadedTexture.dispose()
64
+ }
76
65
  }
77
- }, [input, gl])
78
-
79
- return data
66
+ }, [loadedTexture])
67
+ return (typeof input === 'string' ? loadedTexture : input) ?? null
80
68
  }
81
69
 
82
- function use3DTextureState(
70
+ function useLoad3DTexture(
83
71
  input: string | Data3DTexture | Procedural3DTexture,
84
72
  size: number
85
73
  ): Data3DTexture | Procedural3DTexture | null {
86
- const [data, setData] = useState(typeof input !== 'string' ? input : null)
74
+ const loadedTexture = useMemo(
75
+ () =>
76
+ typeof input === 'string'
77
+ ? new DataTextureLoader(Data3DTexture, parseUint8Array, {
78
+ width: size,
79
+ height: size,
80
+ depth: size,
81
+ format: RedFormat,
82
+ minFilter: LinearFilter,
83
+ magFilter: LinearFilter,
84
+ wrapS: RepeatWrapping,
85
+ wrapT: RepeatWrapping,
86
+ wrapR: RepeatWrapping,
87
+ colorSpace: NoColorSpace
88
+ }).load(input)
89
+ : undefined,
90
+ [input, size]
91
+ )
87
92
  useEffect(() => {
88
- if (typeof input === 'string') {
89
- const Loader = createData3DTextureLoaderClass(parseUint8Array, {
90
- width: size,
91
- height: size,
92
- depth: size,
93
- format: RedFormat,
94
- minFilter: LinearFilter,
95
- magFilter: LinearFilter,
96
- wrapS: RepeatWrapping,
97
- wrapT: RepeatWrapping,
98
- wrapR: RepeatWrapping,
99
- colorSpace: NoColorSpace
100
- })
101
- const loader = new Loader()
102
- ;(async () => {
103
- setData(await loader.loadAsync(input))
104
- })().catch(error => {
105
- console.error(error)
106
- })
107
- } else {
108
- setData(input)
93
+ if (loadedTexture != null) {
94
+ return () => {
95
+ loadedTexture.dispose()
96
+ }
109
97
  }
110
- }, [input, size])
111
-
112
- return data
98
+ }, [loadedTexture])
99
+ return (typeof input === 'string' ? loadedTexture : input) ?? null
113
100
  }
114
101
 
115
- function useSTBNTextureState(
102
+ function useLoadSTBNTexture(
116
103
  input: string | Data3DTexture
117
104
  ): Data3DTexture | null {
118
- const [data, setData] = useState(typeof input !== 'string' ? input : null)
105
+ const loadedTexture = useMemo(
106
+ () =>
107
+ typeof input === 'string' ? new STBNLoader().load(input) : undefined,
108
+ [input]
109
+ )
119
110
  useEffect(() => {
120
- if (typeof input === 'string') {
121
- const loader = new STBNLoader()
122
- ;(async () => {
123
- setData(await loader.loadAsync(input))
124
- })().catch(error => {
125
- console.error(error)
126
- })
127
- } else {
128
- setData(input)
111
+ if (loadedTexture != null) {
112
+ return () => {
113
+ loadedTexture.dispose()
114
+ }
129
115
  }
130
- }, [input])
131
-
132
- return data
116
+ }, [loadedTexture])
117
+ return (typeof input === 'string' ? loadedTexture : input) ?? null
133
118
  }
134
119
 
135
120
  export interface CloudsProps
@@ -154,119 +139,116 @@ export interface CloudsProps
154
139
  stbnTexture?: Data3DTexture | string
155
140
  }
156
141
 
157
- export const Clouds = /*#__PURE__*/ forwardRef<CloudsEffect, CloudsProps>(
158
- function Clouds(
159
- {
160
- disableDefaultLayers = false,
161
- localWeatherTexture: localWeatherTextureProp = DEFAULT_LOCAL_WEATHER_URL,
162
- shapeTexture: shapeTextureProp = DEFAULT_SHAPE_URL,
163
- shapeDetailTexture: shapeDetailTextureProp = DEFAULT_SHAPE_DETAIL_URL,
164
- turbulenceTexture: turbulenceTextureProp = DEFAULT_TURBULENCE_URL,
165
- stbnTexture: stbnTextureProp = DEFAULT_STBN_URL,
166
- children,
167
- ...props
168
- },
169
- forwardedRef
170
- ) {
171
- const { textures, transientStates, ...contextProps } =
172
- useContext(AtmosphereContext)
142
+ export const Clouds: FC<CloudsProps> = ({
143
+ ref: forwardedRef,
144
+ disableDefaultLayers = false,
145
+ localWeatherTexture: localWeatherTextureProp = DEFAULT_LOCAL_WEATHER_URL,
146
+ shapeTexture: shapeTextureProp = DEFAULT_SHAPE_URL,
147
+ shapeDetailTexture: shapeDetailTextureProp = DEFAULT_SHAPE_DETAIL_URL,
148
+ turbulenceTexture: turbulenceTextureProp = DEFAULT_TURBULENCE_URL,
149
+ stbnTexture: stbnTextureProp = DEFAULT_STBN_URL,
150
+ children,
151
+ ...props
152
+ }) => {
153
+ const { textures, transientStates, ...contextProps } =
154
+ useContext(AtmosphereContext)
155
+
156
+ const [atmosphereParameters, others] = separateProps({
157
+ ...cloudsPassOptionsDefaults,
158
+ ...contextProps,
159
+ ...textures,
160
+ ...props
161
+ })
162
+
163
+ const effect = useMemo(() => new CloudsEffect(), [])
164
+ useEffect(() => {
165
+ return () => {
166
+ effect.dispose()
167
+ }
168
+ }, [effect])
173
169
 
174
- const [atmosphereParameters, others] = separateProps({
175
- ...cloudsPassOptionsDefaults,
176
- ...contextProps,
177
- ...textures,
178
- ...props
179
- })
170
+ useFrame(() => {
171
+ if (transientStates != null) {
172
+ effect.sunDirection.copy(transientStates.sunDirection)
173
+ effect.ellipsoidCenter.copy(transientStates.ellipsoidCenter)
174
+ effect.ellipsoidMatrix.copy(transientStates.ellipsoidMatrix)
175
+ }
176
+ })
180
177
 
181
- const effect = useMemo(() => new CloudsEffect(), [])
182
- useEffect(() => {
178
+ useEffect(() => {
179
+ if (transientStates != null) {
180
+ transientStates.overlay = effect.atmosphereOverlay
181
+ transientStates.shadow = effect.atmosphereShadow
182
+ transientStates.shadowLength = effect.atmosphereShadowLength
183
183
  return () => {
184
- effect.dispose()
184
+ transientStates.overlay = null
185
+ transientStates.shadow = null
186
+ transientStates.shadowLength = null
185
187
  }
186
- }, [effect])
187
-
188
- useFrame(() => {
189
- if (transientStates != null) {
190
- effect.sunDirection.copy(transientStates.sunDirection)
191
- effect.ellipsoidCenter.copy(transientStates.ellipsoidCenter)
192
- effect.ellipsoidMatrix.copy(transientStates.ellipsoidMatrix)
193
- }
194
- })
188
+ }
189
+ }, [effect, transientStates])
195
190
 
196
- useEffect(() => {
197
- if (transientStates != null) {
198
- transientStates.overlay = effect.atmosphereOverlay
199
- transientStates.shadow = effect.atmosphereShadow
200
- transientStates.shadowLength = effect.atmosphereShadowLength
201
- return () => {
202
- transientStates.overlay = null
203
- transientStates.shadow = null
204
- transientStates.shadowLength = null
205
- }
191
+ const handleChange = useCallback(
192
+ (event: CloudsEffectChangeEvent) => {
193
+ if (transientStates == null) {
194
+ return
206
195
  }
207
- }, [effect, transientStates])
208
-
209
- const handleChange = useCallback(
210
- (event: CloudsEffectChangeEvent) => {
211
- if (transientStates == null) {
212
- return
213
- }
214
- switch (event.property) {
215
- case 'atmosphereOverlay':
216
- transientStates.overlay = effect.atmosphereOverlay
217
- break
218
- case 'atmosphereShadow':
219
- transientStates.shadow = effect.atmosphereShadow
220
- break
221
- case 'atmosphereShadowLength':
222
- transientStates.shadowLength = effect.atmosphereShadowLength
223
- break
224
- }
225
- },
226
- [effect, transientStates]
227
- )
228
- useEffect(() => {
229
- effect.events.addEventListener('change', handleChange)
230
- return () => {
231
- effect.events.removeEventListener('change', handleChange)
196
+ switch (event.property) {
197
+ case 'atmosphereOverlay':
198
+ transientStates.overlay = effect.atmosphereOverlay
199
+ break
200
+ case 'atmosphereShadow':
201
+ transientStates.shadow = effect.atmosphereShadow
202
+ break
203
+ case 'atmosphereShadowLength':
204
+ transientStates.shadowLength = effect.atmosphereShadowLength
205
+ break
206
+ default:
232
207
  }
233
- }, [effect, handleChange])
234
-
235
- const gl = useThree(({ gl }) => gl)
236
- const localWeatherTexture = useTextureState(localWeatherTextureProp, gl)
237
- const shapeTexture = use3DTextureState(
238
- shapeTextureProp,
239
- CLOUD_SHAPE_TEXTURE_SIZE
240
- )
241
- const shapeDetailTexture = use3DTextureState(
242
- shapeDetailTextureProp,
243
- CLOUD_SHAPE_DETAIL_TEXTURE_SIZE
244
- )
245
- const turbulenceTexture = useTextureState(turbulenceTextureProp, gl)
246
- const stbnTexture = useSTBNTextureState(stbnTextureProp)
247
-
248
- const { camera } = useContext(EffectComposerContext)
249
- return (
250
- <>
251
- <primitive
252
- ref={forwardedRef}
253
- object={effect}
254
- mainCamera={camera}
255
- {...atmosphereParameters}
256
- localWeatherTexture={localWeatherTexture}
257
- shapeTexture={shapeTexture}
258
- shapeDetailTexture={shapeDetailTexture}
259
- turbulenceTexture={turbulenceTexture}
260
- stbnTexture={stbnTexture}
261
- {...others}
262
- />
263
- <CloudLayers
264
- layers={effect.cloudLayers}
265
- disableDefault={disableDefaultLayers}
266
- >
267
- {children}
268
- </CloudLayers>
269
- </>
270
- )
271
- }
272
- )
208
+ },
209
+ [effect, transientStates]
210
+ )
211
+ useEffect(() => {
212
+ effect.events.addEventListener('change', handleChange)
213
+ return () => {
214
+ effect.events.removeEventListener('change', handleChange)
215
+ }
216
+ }, [effect, handleChange])
217
+
218
+ const renderer = useThree(({ gl }) => gl)
219
+ const localWeatherTexture = useLoadTexture(localWeatherTextureProp, renderer)
220
+ const shapeTexture = useLoad3DTexture(
221
+ shapeTextureProp,
222
+ CLOUD_SHAPE_TEXTURE_SIZE
223
+ )
224
+ const shapeDetailTexture = useLoad3DTexture(
225
+ shapeDetailTextureProp,
226
+ CLOUD_SHAPE_DETAIL_TEXTURE_SIZE
227
+ )
228
+ const turbulenceTexture = useLoadTexture(turbulenceTextureProp, renderer)
229
+ const stbnTexture = useLoadSTBNTexture(stbnTextureProp)
230
+
231
+ const { camera } = useContext(EffectComposerContext)
232
+ return (
233
+ <>
234
+ <primitive
235
+ ref={forwardedRef}
236
+ object={effect}
237
+ mainCamera={camera}
238
+ {...atmosphereParameters}
239
+ localWeatherTexture={localWeatherTexture}
240
+ shapeTexture={shapeTexture}
241
+ shapeDetailTexture={shapeDetailTexture}
242
+ turbulenceTexture={turbulenceTexture}
243
+ stbnTexture={stbnTexture}
244
+ {...others}
245
+ />
246
+ <CloudLayers
247
+ layers={effect.cloudLayers}
248
+ disableDefault={disableDefaultLayers}
249
+ >
250
+ {children}
251
+ </CloudLayers>
252
+ </>
253
+ )
254
+ }