@takram/three-geospatial 0.8.0 → 0.9.1

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 (64) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/build/index.cjs +1 -1
  3. package/build/index.cjs.map +1 -1
  4. package/build/index.js +580 -598
  5. package/build/index.js.map +1 -1
  6. package/build/r3f.cjs +1 -1
  7. package/build/r3f.js +1 -1
  8. package/build/shared2.cjs +1 -1
  9. package/build/shared2.cjs.map +1 -1
  10. package/build/shared2.js +27 -212
  11. package/build/shared2.js.map +1 -1
  12. package/build/shared3.cjs +1 -1
  13. package/build/shared3.cjs.map +1 -1
  14. package/build/shared3.js +213 -8
  15. package/build/shared3.js.map +1 -1
  16. package/build/webgpu.cjs +6 -1
  17. package/build/webgpu.cjs.map +1 -1
  18. package/build/webgpu.js +1002 -812
  19. package/build/webgpu.js.map +1 -1
  20. package/package.json +1 -1
  21. package/src/PointOfView.ts +12 -5
  22. package/src/STBNLoader.ts +41 -19
  23. package/src/webgpu/CascadedShadowMapsNode.ts +48 -0
  24. package/src/webgpu/DualMipmapFilterNode.ts +8 -4
  25. package/src/webgpu/FilterNode.ts +3 -2
  26. package/src/webgpu/FnLayout.ts +17 -16
  27. package/src/webgpu/HighpVelocityNode.ts +2 -2
  28. package/src/webgpu/LensFlareNode.ts +1 -1
  29. package/src/webgpu/LensGlareNode.ts +26 -26
  30. package/src/webgpu/LensHaloNode.ts +2 -1
  31. package/src/webgpu/OutputTexture3DNode.ts +10 -1
  32. package/src/webgpu/OutputTextureNode.ts +10 -1
  33. package/src/webgpu/STBNTextureNode.ts +72 -0
  34. package/src/webgpu/ScreenSpaceShadowNode.ts +30 -35
  35. package/src/webgpu/SeparableFilterNode.ts +8 -5
  36. package/src/webgpu/SingleFilterNode.ts +5 -2
  37. package/src/webgpu/StorageTexture3DNode.ts +30 -0
  38. package/src/webgpu/TemporalAntialiasNode.ts +50 -31
  39. package/src/webgpu/accessors.ts +72 -36
  40. package/src/webgpu/debug.ts +38 -47
  41. package/src/webgpu/events.ts +18 -0
  42. package/src/webgpu/index.ts +4 -0
  43. package/src/webgpu/math.ts +116 -15
  44. package/src/webgpu/sampling.ts +39 -5
  45. package/src/webgpu/transformations.ts +71 -44
  46. package/types/PointOfView.d.ts +1 -1
  47. package/types/STBNLoader.d.ts +3 -4
  48. package/types/webgpu/CascadedShadowMapsNode.d.ts +13 -0
  49. package/types/webgpu/DualMipmapFilterNode.d.ts +1 -2
  50. package/types/webgpu/FnLayout.d.ts +4 -4
  51. package/types/webgpu/LensGlareNode.d.ts +1 -1
  52. package/types/webgpu/STBNTextureNode.d.ts +10 -0
  53. package/types/webgpu/ScreenSpaceShadowNode.d.ts +2 -4
  54. package/types/webgpu/SeparableFilterNode.d.ts +2 -3
  55. package/types/webgpu/SingleFilterNode.d.ts +1 -2
  56. package/types/webgpu/StorageTexture3DNode.d.ts +9 -0
  57. package/types/webgpu/TemporalAntialiasNode.d.ts +1 -1
  58. package/types/webgpu/accessors.d.ts +9 -8
  59. package/types/webgpu/debug.d.ts +4 -3
  60. package/types/webgpu/events.d.ts +3 -0
  61. package/types/webgpu/index.d.ts +4 -0
  62. package/types/webgpu/math.d.ts +3 -0
  63. package/types/webgpu/sampling.d.ts +2 -1
  64. package/types/webgpu/transformations.d.ts +7 -10
@@ -20,7 +20,6 @@ import {
20
20
  mix,
21
21
  screenCoordinate,
22
22
  screenUV,
23
- select,
24
23
  sqrt,
25
24
  step,
26
25
  struct,
@@ -77,8 +76,15 @@ function isSupportedCamera(camera: Camera): camera is SupportedCamera {
77
76
  )
78
77
  }
79
78
 
79
+ interface RenderPipelineContext {
80
+ context: {
81
+ onBeforeRenderPipeline?: () => void
82
+ onAfterRenderPipeline?: () => void
83
+ }
84
+ }
85
+
80
86
  interface PostProcessingContext {
81
- context?: {
87
+ context: {
82
88
  onBeforePostProcessing?: () => void
83
89
  onAfterPostProcessing?: () => void
84
90
  }
@@ -101,11 +107,9 @@ const clipAABB = /*#__PURE__*/ FnLayout({
101
107
  const vUnit = vClip.xyz.div(eClip)
102
108
  const absUnit = vUnit.abs().toConst()
103
109
  const maxUnit = max(absUnit.x, absUnit.y, absUnit.z).toConst()
104
- return select(
105
- maxUnit.greaterThan(1),
106
- vec4(pClip, current.a).add(vClip.div(maxUnit)),
107
- history
108
- )
110
+ return maxUnit
111
+ .greaterThan(1)
112
+ .select(vec4(pClip, current.a).add(vClip.div(maxUnit)), history)
109
113
  })
110
114
 
111
115
  const varianceOffsets = [
@@ -166,12 +170,17 @@ const currentDepthStruct = /*#__PURE__*/ struct({
166
170
  })
167
171
 
168
172
  const getCurrentDepth = /*#__PURE__*/ FnVar(
169
- (depthNode: TextureNode, inputCoord: Node<'ivec2'>) => {
173
+ (depthNode: TextureNode, inputCoord: Node<'ivec2'>) => builder => {
170
174
  const closestCoord = ivec2(0).toVar()
171
175
  const closestDepth = float(1).toVar()
172
176
  for (const [x, y] of neighborOffsets) {
173
177
  const neighbor = inputCoord.add(ivec2(x, y)).toConst()
174
- const depth = depthNode.load(neighbor).r.toConst()
178
+ let depth = depthNode.load(neighbor).r
179
+ if (builder.renderer.reversedDepthBuffer) {
180
+ depth = depth.oneMinus()
181
+ }
182
+ depth = depth.toConst()
183
+
175
184
  If(depth.lessThan(closestDepth), () => {
176
185
  closestCoord.assign(neighbor)
177
186
  closestDepth.assign(depth)
@@ -181,7 +190,7 @@ const getCurrentDepth = /*#__PURE__*/ FnVar(
181
190
  }
182
191
  )
183
192
 
184
- const subpixelCorrection = FnVar(
193
+ const subpixelCorrection = /*#__PURE__#*/ FnVar(
185
194
  (velocityUV: Node<'vec2'>, textureSize: Node<'ivec2'>): Node<'float'> => {
186
195
  const velocityTexel = velocityUV.mul(textureSize)
187
196
  const phase = velocityTexel.fract().abs()
@@ -236,18 +245,17 @@ export class TemporalAntialiasNode extends TempNode {
236
245
  velocityThreshold = uniform(0.1)
237
246
  depthError = uniform(0.001)
238
247
 
239
- // Static options:
240
248
  debugShowRejection = false
241
249
 
242
250
  private readonly textureNode: TextureNode
243
251
 
244
- private resolveRT = this.createRenderTarget('Resolve')
245
- private historyRT = this.createRenderTarget('History')
252
+ private resolveRT = this.createRenderTarget('resolve')
253
+ private historyRT = this.createRenderTarget('history')
246
254
  private previousDepthTexture?: DepthTexture
247
255
  private readonly resolveMaterial = new NodeMaterial()
248
256
  private readonly mesh = new QuadMesh()
249
257
  private rendererState?: RendererUtils.RendererState
250
- private needsSyncPostProcessing = false
258
+ private needsSyncRenderPipeline = false
251
259
  private needsClearHistory = false
252
260
 
253
261
  private readonly resolveNode = texture(this.resolveRT.texture)
@@ -263,6 +271,10 @@ export class TemporalAntialiasNode extends TempNode {
263
271
  camera: Camera
264
272
  ) {
265
273
  super('vec4')
274
+ this.updateBeforeType = NodeUpdateType.FRAME
275
+ this.resolveMaterial.name = 'TemporalAntialias_resolve'
276
+ this.mesh.name = 'TemporalAntialias'
277
+
266
278
  this.inputNode = inputNode
267
279
  this.depthNode = depthNode
268
280
  this.velocityNode = velocityNode
@@ -272,8 +284,6 @@ export class TemporalAntialiasNode extends TempNode {
272
284
  this.camera = camera
273
285
 
274
286
  this.textureNode = outputTexture(this, this.resolveRT.texture)
275
-
276
- this.updateBeforeType = NodeUpdateType.FRAME
277
287
  }
278
288
 
279
289
  override customCacheKey(): number {
@@ -292,7 +302,7 @@ export class TemporalAntialiasNode extends TempNode {
292
302
  texture.generateMipmaps = false
293
303
 
294
304
  const typeName = (this.constructor as typeof Node).type
295
- texture.name = name != null ? `${typeName}.${name}` : typeName
305
+ texture.name = name != null ? `${typeName}_${name}` : typeName
296
306
 
297
307
  return renderTarget
298
308
  }
@@ -402,7 +412,7 @@ export class TemporalAntialiasNode extends TempNode {
402
412
  this.swapBuffers()
403
413
 
404
414
  // Don't jitter the camera in subsequent render passes if any:
405
- if (this.needsSyncPostProcessing) {
415
+ if (this.needsSyncRenderPipeline) {
406
416
  this.clearViewOffset()
407
417
  }
408
418
  }
@@ -411,7 +421,7 @@ export class TemporalAntialiasNode extends TempNode {
411
421
  const getPreviousDepth = (uv: Node<'vec2'>): Node<'float'> => {
412
422
  const { previousDepthNode: depthNode } = this
413
423
  const depth = depthNode
414
- .load(ivec2(uv.mul(textureSize(depthNode)).sub(0.5)))
424
+ .load(ivec2(uv.mul(depthNode.size()).sub(0.5)))
415
425
  .toConst()
416
426
  return renderer.logarithmicDepthBuffer
417
427
  ? logarithmicToPerspectiveDepth(
@@ -419,7 +429,9 @@ export class TemporalAntialiasNode extends TempNode {
419
429
  cameraNear(this.camera),
420
430
  cameraFar(this.camera)
421
431
  )
422
- : depth
432
+ : renderer.reversedDepthBuffer
433
+ ? depth.oneMinus()
434
+ : depth
423
435
  }
424
436
 
425
437
  return Fn(() => {
@@ -485,7 +497,7 @@ export class TemporalAntialiasNode extends TempNode {
485
497
  // Reference: https://github.com/simco50/D3D12_Research/
486
498
  const temporalAlpha = mix(
487
499
  this.temporalAlpha,
488
- 0.8,
500
+ 0.4,
489
501
  subpixelCorrection(velocityUVW.xy, textureSize(this.inputNode))
490
502
  ).saturate()
491
503
 
@@ -501,16 +513,23 @@ export class TemporalAntialiasNode extends TempNode {
501
513
  }
502
514
 
503
515
  override setup(builder: NodeBuilder): unknown {
504
- const { context } = (builder.context.postProcessing ??
505
- {}) as PostProcessingContext
506
- if (context != null) {
507
- const { onBeforePostProcessing } = context
508
- context.onBeforePostProcessing = () => {
509
- onBeforePostProcessing?.()
510
- const size = builder.renderer.getDrawingBufferSize(sizeScratch)
511
- this.setViewOffset(size.width, size.height)
512
- }
513
- this.needsSyncPostProcessing = true
516
+ // We have to take care of the renaming of PostProcessing to RenderPipeline
517
+ // in r183, as well as changes to property fields in the context.
518
+ const onBeforeRenderPipeline = (): void => {
519
+ const size = builder.renderer.getDrawingBufferSize(sizeScratch)
520
+ this.setViewOffset(size.width, size.height)
521
+ }
522
+ if (builder.context.renderPipeline != null) {
523
+ const { context } = builder.context
524
+ .renderPipeline as RenderPipelineContext
525
+ context.onBeforeRenderPipeline = onBeforeRenderPipeline
526
+ this.needsSyncRenderPipeline = true
527
+ }
528
+ if (builder.context.postProcessing != null) {
529
+ const { context } = builder.context
530
+ .postProcessing as PostProcessingContext
531
+ context.onBeforePostProcessing = onBeforeRenderPipeline
532
+ this.needsSyncRenderPipeline = true
514
533
  }
515
534
 
516
535
  const { resolveMaterial } = this
@@ -1,5 +1,17 @@
1
- import { Vector3, type Camera } from 'three'
2
- import { reference, uniform } from 'three/tsl'
1
+ import type { Camera, Vector3 } from 'three'
2
+ import {
3
+ cameraFar as cameraFarTSL,
4
+ cameraNear as cameraNearTSL,
5
+ cameraPosition,
6
+ cameraProjectionMatrix,
7
+ cameraProjectionMatrixInverse,
8
+ cameraViewMatrix,
9
+ cameraWorldMatrix,
10
+ Fn,
11
+ positionView,
12
+ reference,
13
+ uniform
14
+ } from 'three/tsl'
3
15
  import type { UniformNode } from 'three/webgpu'
4
16
 
5
17
  import type { Node } from './node'
@@ -23,43 +35,67 @@ function getCache<T extends {}, U extends {}>(
23
35
  return (cache[name] ??= callback()) as U
24
36
  }
25
37
 
26
- export const projectionMatrix = (camera: Camera): Node<'mat4'> =>
27
- getCache(camera, 'projectionMatrix', () =>
28
- reference('projectionMatrix', 'mat4', camera).setName('projectionMatrix')
29
- )
38
+ export const projectionMatrix = (camera?: Camera | null): Node<'mat4'> =>
39
+ camera != null
40
+ ? getCache(camera, 'projectionMatrix', () =>
41
+ reference('projectionMatrix', 'mat4', camera).setName(
42
+ 'projectionMatrix'
43
+ )
44
+ )
45
+ : cameraProjectionMatrix
30
46
 
31
- export const viewMatrix = (camera: Camera): Node<'mat4'> =>
32
- getCache(camera, 'viewMatrix', () =>
33
- reference('matrixWorldInverse', 'mat4', camera).setName('viewMatrix')
34
- )
47
+ export const viewMatrix = (camera?: Camera | null): Node<'mat4'> =>
48
+ camera != null
49
+ ? getCache(camera, 'viewMatrix', () =>
50
+ reference('matrixWorldInverse', 'mat4', camera).setName('viewMatrix')
51
+ )
52
+ : cameraViewMatrix
35
53
 
36
- export const inverseProjectionMatrix = (camera: Camera): Node<'mat4'> =>
37
- getCache(camera, 'inverseProjectionMatrix', () =>
38
- reference('projectionMatrixInverse', 'mat4', camera).setName(
39
- 'inverseProjectionMatrix'
40
- )
41
- )
54
+ export const inverseProjectionMatrix = (
55
+ camera?: Camera | null
56
+ ): Node<'mat4'> =>
57
+ camera != null
58
+ ? getCache(camera, 'inverseProjectionMatrix', () =>
59
+ reference('projectionMatrixInverse', 'mat4', camera).setName(
60
+ 'inverseProjectionMatrix'
61
+ )
62
+ )
63
+ : cameraProjectionMatrixInverse
42
64
 
43
- export const inverseViewMatrix = (camera: Camera): Node<'mat4'> =>
44
- getCache(camera, 'inverseViewMatrix', () =>
45
- reference('matrixWorld', 'mat4', camera).setName('inverseViewMatrix')
46
- )
65
+ export const inverseViewMatrix = (camera?: Camera | null): Node<'mat4'> =>
66
+ camera != null
67
+ ? getCache(camera, 'inverseViewMatrix', () =>
68
+ reference('matrixWorld', 'mat4', camera).setName('inverseViewMatrix')
69
+ )
70
+ : cameraWorldMatrix // TODO: Not always
47
71
 
48
- export const cameraPositionWorld = (camera: Camera): UniformNode<Vector3> =>
49
- getCache(camera, 'cameraPositionWorld', () =>
50
- uniform(new Vector3())
51
- .setName('cameraPositionWorld')
52
- .onRenderUpdate((_, { value }) => {
53
- value.setFromMatrixPosition(camera.matrixWorld)
54
- })
55
- )
72
+ export const cameraPositionWorld = (
73
+ camera?: Camera | null
74
+ ): UniformNode<Vector3> =>
75
+ camera != null
76
+ ? getCache(camera, 'cameraPositionWorld', () =>
77
+ uniform('vec3')
78
+ .setName('cameraPositionWorld')
79
+ .onRenderUpdate((_, { value }) => {
80
+ value.setFromMatrixPosition(camera.matrixWorld)
81
+ })
82
+ )
83
+ : cameraPosition
56
84
 
57
- export const cameraNear = (camera: Camera): Node<'float'> =>
58
- getCache(camera, 'cameraNear', () =>
59
- reference('near', 'float', camera).setName('cameraNear')
60
- )
85
+ export const cameraNear = (camera?: Camera | null): Node<'float'> =>
86
+ camera != null
87
+ ? getCache(camera, 'cameraNear', () =>
88
+ reference('near', 'float', camera).setName('cameraNear')
89
+ )
90
+ : cameraNearTSL
61
91
 
62
- export const cameraFar = (camera: Camera): Node<'float'> =>
63
- getCache(camera, 'cameraFar', () =>
64
- reference('far', 'float', camera).setName('cameraFar')
65
- )
92
+ export const cameraFar = (camera?: Camera | null): Node<'float'> =>
93
+ camera != null
94
+ ? getCache(camera, 'cameraFar', () =>
95
+ reference('far', 'float', camera).setName('cameraFar')
96
+ )
97
+ : cameraFarTSL
98
+
99
+ export const viewZ = Fn((): Node<'float'> => positionView.z)
100
+ .once()()
101
+ .toVar('viewZ')
@@ -6,65 +6,56 @@ import { QuadGeometry } from '../QuadGeometry'
6
6
 
7
7
  async function debugShader(
8
8
  renderer: Renderer,
9
- mesh: Mesh
10
- ): Promise<
11
- Awaited<{
12
- fragmentShader: string | null
13
- vertexShader: string | null
14
- }>
15
- > {
16
- return await renderer.debug
17
- .getShaderAsync(new Scene(), new Camera(), mesh)
18
- .then(result => {
19
- return result
20
- })
21
- .catch((error: unknown) => {
22
- console.error(error)
23
- return { fragmentShader: null, vertexShader: null }
24
- })
9
+ material: NodeMaterial
10
+ ): Promise<{
11
+ vertexShader: string | null
12
+ fragmentShader: string | null
13
+ }> {
14
+ const mesh = new Mesh(new QuadGeometry(), material)
15
+ try {
16
+ return await renderer.debug.getShaderAsync(new Scene(), new Camera(), mesh)
17
+ } catch (error: unknown) {
18
+ console.error(error)
19
+ return { vertexShader: null, fragmentShader: null }
20
+ } finally {
21
+ mesh.geometry.dispose()
22
+ }
25
23
  }
26
24
 
27
- export function debugFragmentNode(
25
+ export async function debugMaterial(
28
26
  renderer: Renderer,
29
27
  material: NodeMaterial
30
- ): void {
31
- const mesh = new Mesh(new QuadGeometry(), material)
32
- void debugShader(renderer, mesh)
33
- .then(result => {
34
- console.log(result.fragmentShader)
35
- })
36
- .finally(() => {
37
- mesh.geometry.dispose()
38
- })
28
+ ): Promise<string | null> {
29
+ const { vertexShader, fragmentShader } = await debugShader(renderer, material)
30
+ return vertexShader != null && fragmentShader != null
31
+ ? `// Vertex shader\n\n${vertexShader}\n// Fragment shader\n\n${fragmentShader}`
32
+ : null
39
33
  }
40
34
 
41
- export function debugVertexNode(
35
+ export async function debugVertexNode(
42
36
  renderer: Renderer,
43
37
  material: NodeMaterial
44
- ): void {
45
- const mesh = new Mesh(new QuadGeometry(), material)
46
- void debugShader(renderer, mesh)
47
- .then(result => {
48
- console.log(result.vertexShader)
49
- })
50
- .finally(() => {
51
- mesh.geometry.dispose()
52
- })
38
+ ): Promise<string | null> {
39
+ return (await debugShader(renderer, material)).vertexShader
40
+ }
41
+
42
+ export async function debugFragmentNode(
43
+ renderer: Renderer,
44
+ material: NodeMaterial
45
+ ): Promise<string | null> {
46
+ return (await debugShader(renderer, material)).fragmentShader
53
47
  }
54
48
 
55
- export function debugNode(renderer: Renderer, node: Node): void {
49
+ export async function debugNode(
50
+ renderer: Renderer,
51
+ node: Node
52
+ ): Promise<string | null> {
56
53
  const material = new NodeMaterial()
57
54
  material.vertexNode = vec4(positionGeometry.xy, 0, 1)
58
- material.fragmentNode = node
59
- const mesh = new Mesh(new QuadGeometry(), material)
60
- void debugShader(renderer, mesh)
61
- .then(result => {
62
- console.log(result.fragmentShader)
63
- })
64
- .finally(() => {
65
- material.dispose()
66
- mesh.geometry.dispose()
67
- })
55
+ material.fragmentNode = node.toConst('debugNode')
56
+ const shader = await debugShader(renderer, material)
57
+ material.dispose()
58
+ return shader.fragmentShader
68
59
  }
69
60
 
70
61
  export function hookFunction<
@@ -0,0 +1,18 @@
1
+ import { OnBeforeObjectUpdate, OnObjectUpdate } from 'three/tsl'
2
+ import { NodeUpdateType, type Node, type NodeFrame } from 'three/webgpu'
3
+
4
+ // TODO: File a PR for these:
5
+
6
+ export const OnFrameUpdate = (callback: (frame: NodeFrame) => void): Node => {
7
+ const node = OnObjectUpdate(callback)
8
+ node.updateType = NodeUpdateType.NONE
9
+ return node
10
+ }
11
+
12
+ export const OnBeforeFrameUpdate = (
13
+ callback: (frame: NodeFrame) => void
14
+ ): Node => {
15
+ const node = OnBeforeObjectUpdate(callback)
16
+ node.updateBeforeType = NodeUpdateType.FRAME
17
+ return node
18
+ }
@@ -1,6 +1,8 @@
1
1
  export * from './accessors'
2
+ export * from './CascadedShadowMapsNode'
2
3
  export * from './debug'
3
4
  export * from './DownsampleThresholdNode'
5
+ export * from './events'
4
6
  export * from './FnLayout'
5
7
  export * from './FnVar'
6
8
  export * from './GaussianBlurNode'
@@ -16,6 +18,8 @@ export * from './OutputTexture3DNode'
16
18
  export * from './OutputTextureNode'
17
19
  export * from './sampling'
18
20
  export * from './ScreenSpaceShadowNode'
21
+ export * from './STBNTextureNode'
22
+ export * from './StorageTexture3DNode'
19
23
  export * from './TemporalAntialiasNode'
20
24
  export * from './transformations'
21
25
  export * from './utils'
@@ -1,8 +1,110 @@
1
- import { dot, If, sqrt, struct, vec2, vec4 } from 'three/tsl'
1
+ import {
2
+ bool,
3
+ bvec2,
4
+ bvec3,
5
+ bvec4,
6
+ dot,
7
+ If,
8
+ overloadingFn,
9
+ sqrt,
10
+ struct,
11
+ uvec2,
12
+ uvec3,
13
+ uvec4,
14
+ vec2
15
+ } from 'three/tsl'
2
16
 
17
+ import { FnLayout } from './FnLayout'
3
18
  import { FnVar } from './FnVar'
4
19
  import type { Node } from './node'
5
20
 
21
+ const bvec2Not = /*#__PURE__*/ FnLayout({
22
+ name: 'bvec2Not',
23
+ type: 'bvec2',
24
+ inputs: [{ name: 'x', type: 'bvec2' }]
25
+ })(([x]) => x.notEqual(bool(true)))
26
+
27
+ const bvec3Not = /*#__PURE__*/ FnLayout({
28
+ name: 'bvec3Not',
29
+ type: 'bvec3',
30
+ inputs: [{ name: 'x', type: 'bvec3' }]
31
+ })(([x]) => x.notEqual(bool(true)))
32
+
33
+ const bvec4Not = /*#__PURE__*/ FnLayout({
34
+ name: 'bvec4Not',
35
+ type: 'bvec4',
36
+ inputs: [{ name: 'x', type: 'bvec4' }]
37
+ })(([x]) => x.notEqual(bool(true)))
38
+
39
+ // WORKAROUND: PR on this https://github.com/mrdoob/three.js/pull/33442
40
+ export const bvecNot = /*#__PURE__*/ overloadingFn([
41
+ bvec2Not,
42
+ bvec3Not,
43
+ bvec4Not
44
+ ])
45
+
46
+ const bvec2And = /*#__PURE__*/ FnLayout({
47
+ name: 'bvec2And',
48
+ type: 'bvec2',
49
+ inputs: [
50
+ { name: 'x', type: 'bvec2' },
51
+ { name: 'y', type: 'bvec2' }
52
+ ]
53
+ })(([x, y]) => bvec2(uvec2(x).mul(uvec2(y))))
54
+
55
+ const bvec3And = /*#__PURE__*/ FnLayout({
56
+ name: 'bvec3And',
57
+ type: 'bvec3',
58
+ inputs: [
59
+ { name: 'x', type: 'bvec3' },
60
+ { name: 'y', type: 'bvec3' }
61
+ ]
62
+ })(([x, y]) => bvec3(uvec3(x).mul(uvec3(y))))
63
+
64
+ const bvec4And = /*#__PURE__*/ FnLayout({
65
+ name: 'bvec4And',
66
+ type: 'bvec4',
67
+ inputs: [
68
+ { name: 'x', type: 'bvec4' },
69
+ { name: 'y', type: 'bvec4' }
70
+ ]
71
+ })(([x, y]) => bvec4(uvec4(x).mul(uvec4(y))))
72
+
73
+ export const bvecAnd = /*#__PURE__*/ overloadingFn([
74
+ bvec2And,
75
+ bvec3And,
76
+ bvec4And
77
+ ])
78
+
79
+ const bvec2Or = /*#__PURE__*/ FnLayout({
80
+ name: 'bvec2Or',
81
+ type: 'bvec2',
82
+ inputs: [
83
+ { name: 'x', type: 'bvec2' },
84
+ { name: 'y', type: 'bvec2' }
85
+ ]
86
+ })(([x, y]) => uvec2(x).add(uvec2(y)).notEqual(0))
87
+
88
+ const bvec3Or = /*#__PURE__*/ FnLayout({
89
+ name: 'bvec3Or',
90
+ type: 'bvec3',
91
+ inputs: [
92
+ { name: 'x', type: 'bvec3' },
93
+ { name: 'y', type: 'bvec3' }
94
+ ]
95
+ })(([x, y]) => uvec3(x).add(uvec3(y)).notEqual(0))
96
+
97
+ const bvec4Or = /*#__PURE__*/ FnLayout({
98
+ name: 'bvec4Or',
99
+ type: 'bvec4',
100
+ inputs: [
101
+ { name: 'x', type: 'bvec4' },
102
+ { name: 'y', type: 'bvec4' }
103
+ ]
104
+ })(([x, y]) => uvec4(x).add(uvec4(y)).notEqual(0))
105
+
106
+ export const bvecOr = /*#__PURE__*/ overloadingFn([bvec2Or, bvec3Or, bvec4Or])
107
+
6
108
  // Reference: https://iquilezles.org/articles/intersectors/
7
109
 
8
110
  export const raySphereIntersection = /*#__PURE__*/ FnVar(
@@ -15,7 +117,7 @@ export const raySphereIntersection = /*#__PURE__*/ FnVar(
15
117
  const a = rayOrigin.sub(center)
16
118
  const b = dot(rayDirection, a)
17
119
  const c = dot(a, a).sub(radius.pow2())
18
- const discriminant = b.pow2().sub(c).toVar()
120
+ const discriminant = b.pow2().sub(c).toConst()
19
121
 
20
122
  const intersection = vec2(-1)
21
123
  If(discriminant.greaterThanEqual(0), () => {
@@ -26,10 +128,10 @@ export const raySphereIntersection = /*#__PURE__*/ FnVar(
26
128
  }
27
129
  )
28
130
 
29
- export const raySpheresIntersectionsStruct = /*#__PURE__*/ struct(
30
- { near: 'vec4', far: 'vec4' },
31
- 'raySpheresIntersections'
32
- )
131
+ export const raySpheresIntersectionsStruct = /*#__PURE__*/ struct({
132
+ near: 'vec4',
133
+ far: 'vec4'
134
+ })
33
135
 
34
136
  // Derive ray-sphere intersections with multiple radii at once:
35
137
  export const raySpheresIntersections = /*#__PURE__*/ FnVar(
@@ -42,15 +144,14 @@ export const raySpheresIntersections = /*#__PURE__*/ FnVar(
42
144
  const a = rayOrigin.sub(center)
43
145
  const b = dot(rayDirection, a)
44
146
  const c = dot(a, a).sub(radii.pow2())
45
- const discriminant = b.pow2().sub(c).toVar()
147
+ const discriminant = b.pow2().sub(c).toConst()
46
148
 
47
- const near = vec4(-1)
48
- const far = vec4(-1)
49
- If(discriminant.greaterThanEqual(0), () => {
50
- const Q = sqrt(discriminant)
51
- near.assign(b.negate().sub(Q))
52
- far.assign(b.negate().add(Q))
53
- })
149
+ // Reference: https://github.com/GameTechDev/OutdoorLightScattering/blob/master/fx/Common.fxh#L148
150
+ const mask = vec2(discriminant.greaterThanEqual(0)).toConst()
151
+ const inverseMask = mask.oneMinus().toConst()
152
+ const Q = sqrt(discriminant.max(0)).toConst()
153
+ const near = mask.mul(b.negate().sub(Q)).sub(inverseMask)
154
+ const far = mask.mul(b.negate().add(Q)).sub(inverseMask)
54
155
  return raySpheresIntersectionsStruct(near, far)
55
156
  }
56
157
  )
@@ -69,7 +170,7 @@ export const rayEllipsoidIntersection = /*#__PURE__*/ FnVar(
69
170
  const discriminant = b
70
171
  .pow2()
71
172
  .sub(a.mul(c.sub(1)))
72
- .toVar()
173
+ .toConst()
73
174
 
74
175
  const intersections = vec2(-1)
75
176
  If(discriminant.greaterThanEqual(0), () => {
@@ -1,16 +1,50 @@
1
- import { add, sub, textureSize, vec2 } from 'three/tsl'
2
- import type { TextureNode } from 'three/webgpu'
1
+ import { add, ivec2, ivec4, sub, uv, vec2, vec4 } from 'three/tsl'
2
+ import type { ConstNode, TextureNode } from 'three/webgpu'
3
3
 
4
+ import { reinterpretType } from '../types'
4
5
  import { FnVar } from './FnVar'
5
6
  import type { Node } from './node'
6
7
 
8
+ const components = ['x', 'y', 'z', 'w'] as const
9
+
10
+ // WORKAROUND: TextureNode doesn't have gather() yet.
11
+ // See: https://www.w3.org/TR/WGSL/#texturegather
12
+ export const textureGather = /*#__PURE__*/ FnVar(
13
+ (
14
+ textureNode: TextureNode,
15
+ uvNode: Node<'vec2'>,
16
+ component = 0
17
+ ): Node<'vec4'> => {
18
+ let componentValue
19
+ if (typeof component === 'number') {
20
+ componentValue = component
21
+ } else if ((component as any)?.isConstNode === true) {
22
+ reinterpretType<ConstNode<number>>(component)
23
+ componentValue = component.value
24
+ } else {
25
+ throw new Error('Component must be a constant.')
26
+ }
27
+
28
+ const size = textureNode.size()
29
+ const coord = ivec2(uvNode.mul(size).sub(0.5).floor()).toConst()
30
+ const i = ivec4(coord, coord.add(1)).toConst()
31
+ const c = components[componentValue] // element() fails for depth textures
32
+ return vec4(
33
+ textureNode.load(i.xw)[c], // min, max
34
+ textureNode.load(i.zw)[c], // max, max
35
+ textureNode.load(i.zy)[c], // max, min
36
+ textureNode.load(i.xy)[c] // min, min
37
+ )
38
+ }
39
+ )
40
+
7
41
  // 9-taps version of Catmull-Rom sampling.
8
42
  // Reference: https://gist.github.com/TheRealMJP/c83b8c0f46b63f3a88a5986f4fa982b1
9
43
  export const textureCatmullRom = /*#__PURE__*/ FnVar(
10
- (textureNode: TextureNode, uv: Node<'vec2'>): Node<'vec4'> => {
11
- const size = vec2(textureSize(textureNode))
44
+ (textureNode: TextureNode, uvNode: Node<'vec2'> = uv()): Node<'vec4'> => {
45
+ const size = vec2(textureNode.size())
12
46
  const texelSize = size.reciprocal()
13
- const position = uv.mul(size)
47
+ const position = uvNode.mul(size)
14
48
  const centerPosition = position.sub(0.5).floor().add(0.5)
15
49
 
16
50
  // Compute the fractional offset from our starting texel to our original