@wave3d/core 0.8.0 → 0.10.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 (71) hide show
  1. package/README.md +3 -1
  2. package/dist/config/model.d.ts +48 -2
  3. package/dist/config/model.js +8 -1
  4. package/dist/config/model.js.map +1 -1
  5. package/dist/index.d.ts +3 -2
  6. package/dist/renderer/WaveRenderer.d.ts +142 -10
  7. package/dist/renderer/WaveRenderer.js +209 -53
  8. package/dist/renderer/WaveRenderer.js.map +1 -1
  9. package/dist/renderer/WaveRendererGPU.js +291 -0
  10. package/dist/renderer/WaveRendererGPU.js.map +1 -0
  11. package/dist/renderer/gpu-loader.js +2 -0
  12. package/dist/renderer/index.d.ts +3 -2
  13. package/dist/renderer/interaction.d.ts +29 -0
  14. package/dist/renderer/interaction.js +77 -31
  15. package/dist/renderer/interaction.js.map +1 -1
  16. package/dist/renderer/interactionGates.js +59 -0
  17. package/dist/renderer/interactionGates.js.map +1 -0
  18. package/dist/renderer/particleField.d.ts +1 -59
  19. package/dist/renderer/particleField.js +49 -29
  20. package/dist/renderer/particleField.js.map +1 -1
  21. package/dist/renderer/particleFieldGPU.js +141 -0
  22. package/dist/renderer/particleFieldGPU.js.map +1 -0
  23. package/dist/renderer/tilt.d.ts +17 -0
  24. package/dist/renderer/tilt.js +124 -0
  25. package/dist/renderer/tilt.js.map +1 -0
  26. package/dist/renderer/tsl/color.js +129 -0
  27. package/dist/renderer/tsl/color.js.map +1 -0
  28. package/dist/renderer/tsl/noise.js +83 -0
  29. package/dist/renderer/tsl/noise.js.map +1 -0
  30. package/dist/renderer/tsl/packedArray.js +76 -0
  31. package/dist/renderer/tsl/packedArray.js.map +1 -0
  32. package/dist/renderer/tsl/particleMaterial.js +133 -0
  33. package/dist/renderer/tsl/particleMaterial.js.map +1 -0
  34. package/dist/renderer/tsl/particleUniforms.js +44 -0
  35. package/dist/renderer/tsl/particleUniforms.js.map +1 -0
  36. package/dist/renderer/tsl/pointerField.js +73 -0
  37. package/dist/renderer/tsl/pointerField.js.map +1 -0
  38. package/dist/renderer/tsl/post.js +63 -0
  39. package/dist/renderer/tsl/post.js.map +1 -0
  40. package/dist/renderer/tsl/postChain.js +56 -0
  41. package/dist/renderer/tsl/postChain.js.map +1 -0
  42. package/dist/renderer/tsl/postEffects.js +221 -0
  43. package/dist/renderer/tsl/postEffects.js.map +1 -0
  44. package/dist/renderer/tsl/types.js +28 -0
  45. package/dist/renderer/tsl/types.js.map +1 -0
  46. package/dist/renderer/tsl/uniforms.js +152 -0
  47. package/dist/renderer/tsl/uniforms.js.map +1 -0
  48. package/dist/renderer/tsl/waveMaterial.js +185 -0
  49. package/dist/renderer/tsl/waveMaterial.js.map +1 -0
  50. package/dist/renderer/tsl/waveShape.js +108 -0
  51. package/dist/renderer/tsl/waveShape.js.map +1 -0
  52. package/dist/shell/createWave.d.ts +29 -0
  53. package/dist/shell/createWave.js +39 -11
  54. package/dist/shell/createWave.js.map +1 -1
  55. package/dist/shell/probe.js +18 -1
  56. package/dist/shell/probe.js.map +1 -1
  57. package/dist/standalone/wave3d.standalone.js +1811 -1594
  58. package/dist/standalone/wave3d.standalone.webgpu.js +36647 -0
  59. package/dist/standalone.d.ts +10 -3
  60. package/dist/standalone.js +10 -3
  61. package/dist/standalone.js.map +1 -1
  62. package/dist/studio/StudioWaveRenderer.d.ts +4 -0
  63. package/dist/studio/StudioWaveRenderer.js +9 -0
  64. package/dist/studio/StudioWaveRenderer.js.map +1 -1
  65. package/dist/studio/StudioWaveRendererGPU.js +18 -0
  66. package/dist/studio/StudioWaveRendererGPU.js.map +1 -0
  67. package/dist/studio/index.d.ts +12 -2
  68. package/dist/studio/index.js +14 -1
  69. package/dist/studio/index.js.map +1 -0
  70. package/package.json +12 -3
  71. package/skills/wave3d/SKILL.md +29 -2
@@ -0,0 +1,221 @@
1
+ import { Fn, Loop, clamp, cos, dot, exp, float, floor, fract, fwidth, int, length, mat2, max, mix, screenSize, sin, smoothstep, sqrt, transpose, uniformArray, uv, vec2, vec3, vec4 } from "three/tsl";
2
+ //#region src/renderer/tsl/postEffects.ts
3
+ /**
4
+ * The optional post effects, in TSL — ports of the layered passes in `../shaders.ts`.
5
+ *
6
+ * On the WebGL path each of these is a `ShaderPass` with its own render target, so it can sample
7
+ * the previous stage at ANY coordinate (`texture2D(tDiffuse, someOtherUv)`). A node graph has no
8
+ * such target by default: a composed node is an expression evaluated at the current fragment, and
9
+ * cannot be sampled elsewhere. Every effect below that reads an offset coordinate — the light
10
+ * march, the halftone cell centre, the dither block centre — therefore takes a TEXTURE node, and
11
+ * the chain in `postChain.ts` materialises the preceding stage with `convertToTexture()`.
12
+ *
13
+ * Effects that only read the current fragment (heatmap, paper texture, CMYK halftone) take a plain
14
+ * colour node and cost nothing extra.
15
+ */
16
+ /** Rec.709 luminance, as the GLSL `luma`. */
17
+ const luma = (c) => dot(c, vec3(.2126, .7152, .0722));
18
+ /**
19
+ * The fragment coordinate in the GLSL convention (origin BOTTOM-left).
20
+ *
21
+ * `screenCoordinate` follows WebGPU's top-left origin and flips Y on WebGL to match, so every
22
+ * effect keyed off `gl_FragCoord` has to flip back or its pattern lands mirrored. Screen-space
23
+ * dot grids and dither matrices are extremely visible when they do.
24
+ */
25
+ function fragCoord(screenCoordinate) {
26
+ return vec2(screenCoordinate.x, screenSize.y.sub(screenCoordinate.y));
27
+ }
28
+ const LIGHT_SAMPLES = 24;
29
+ /**
30
+ * March from each pixel toward the light point, accumulating the wave's own brightness weighted by
31
+ * alpha (so only opaque pixels emit), then add the streaks back. Runs in the scene zone, so it
32
+ * scatters the raw pre-tone-map wave the way bloom does.
33
+ */
34
+ function innerLight(sample, u) {
35
+ return Fn(() => {
36
+ const vUv = uv();
37
+ const src = sample(vUv).toVar("ilSrc");
38
+ const delta = vUv.sub(u.uInnerLightCenter).mul(u.uInnerLightDensity.div(LIGHT_SAMPLES)).toVar("ilDelta");
39
+ const coord = vUv.toVar("ilCoord");
40
+ const decay = float(1).toVar("ilDecay");
41
+ const rays = vec3(0).toVar("ilRays");
42
+ Loop({
43
+ start: 0,
44
+ end: LIGHT_SAMPLES,
45
+ type: "int"
46
+ }, () => {
47
+ coord.subAssign(delta);
48
+ const s = sample(coord).toVar();
49
+ rays.addAssign(s.rgb.mul(s.a).mul(decay));
50
+ decay.mulAssign(u.uInnerLightDecay);
51
+ });
52
+ rays.divAssign(float(LIGHT_SAMPLES));
53
+ return vec4(src.rgb.add(rays.mul(u.uInnerLight)), clamp(max(src.a, luma(rays).mul(u.uInnerLight)), 0, 1));
54
+ })();
55
+ }
56
+ const sigmoid = (x, k) => float(1).div(float(1).add(exp(x.sub(.5).mul(-k))));
57
+ /** paper's classic dot: radius grows as the sampled cell darkens, soft edge via fwidth. */
58
+ const getCircle = (cellUv, lum, baseR) => {
59
+ const r = mix(float(.25 * baseR), 0, lum);
60
+ const d = length(cellUv.sub(.5)).toVar();
61
+ const aa = fwidth(d);
62
+ return float(1).sub(smoothstep(r.sub(aa), r.add(aa), d));
63
+ };
64
+ function halftone(sample, u, coord) {
65
+ return Fn(() => {
66
+ const ca = cos(u.uHalftoneAngle);
67
+ const sa = sin(u.uHalftoneAngle);
68
+ const rot = mat2(ca, sa.negate(), sa, ca).toVar("htRot");
69
+ const cell = max(u.uHalftoneCell, 2).toVar("htCell");
70
+ const gridPx = rot.mul(coord).toVar("htGrid");
71
+ const cellId = floor(gridPx.div(cell));
72
+ const inCell = fract(gridPx.div(cell));
73
+ const tex = sample(transpose(rot).mul(cellId.add(.5).mul(cell)).div(max(screenSize, vec2(1)))).toVar("htTex");
74
+ const c = vec3(sigmoid(tex.r, 2), sigmoid(tex.g, 2), sigmoid(tex.b, 2));
75
+ const lum = mix(float(1), luma(c), tex.a).toVar("htLum");
76
+ const d = getCircle(inCell, lum, 1.3);
77
+ const dots = vec4(tex.rgb, tex.a.mul(d));
78
+ return mix(sample(uv()), dots, clamp(u.uHalftone, 0, 1));
79
+ })();
80
+ }
81
+ const heat = (t) => {
82
+ const s = clamp(t, 0, 1).toVar();
83
+ const c = mix(vec3(0, 0, .4), vec3(0, .6, 1), smoothstep(0, .25, s)).toVar("heatC");
84
+ c.assign(mix(c, vec3(0, 1, .4), smoothstep(.25, .5, s)));
85
+ c.assign(mix(c, vec3(1, 1, 0), smoothstep(.5, .75, s)));
86
+ c.assign(mix(c, vec3(1, .1, 0), smoothstep(.75, 1, s)));
87
+ return c;
88
+ };
89
+ function heatmap(src, uHeatmap) {
90
+ return Fn(() => {
91
+ const s = src.toVar("hmSrc");
92
+ const l = dot(s.rgb, vec3(.299, .587, .114));
93
+ return vec4(mix(s.rgb, heat(l), clamp(uHeatmap, 0, 1)), s.a);
94
+ })();
95
+ }
96
+ const h21 = (p) => fract(sin(dot(p, vec2(127.1, 311.7))).mul(43758.5453));
97
+ function paperTexture(src, uPaper, uPaperScale, coord) {
98
+ return Fn(() => {
99
+ const s = src.toVar("ptSrc");
100
+ const p = coord.div(max(uPaperScale, .5)).toVar("ptP");
101
+ const tex = mix(h21(floor(p)).mul(.5).add(h21(floor(p.mul(vec2(.3, 3)))).mul(.5)), h21(coord), .3);
102
+ const shade = float(1).sub(tex.sub(.5).mul(.35));
103
+ return vec4(s.rgb.mul(mix(float(1), shade, clamp(uPaper, 0, 1))), s.a);
104
+ })();
105
+ }
106
+ /** One rotated halftone dot screen for a channel value. */
107
+ const dotScreen = (coord, value, angle, cell) => {
108
+ const ca = Math.cos(angle);
109
+ const sa = Math.sin(angle);
110
+ const c = fract(mat2(ca, -sa, sa, ca).mul(coord).div(max(cell, 2))).sub(.5).toVar();
111
+ const radius = sqrt(clamp(value, 0, 1)).mul(.5).toVar();
112
+ return smoothstep(radius, radius.sub(.06), length(c));
113
+ };
114
+ function halftoneCmyk(src, uHalftoneCmyk, uCell, coord) {
115
+ return Fn(() => {
116
+ const s = src.toVar("cmykSrc");
117
+ const k = float(1).sub(max(max(s.r, s.g), s.b)).toVar("cmykK");
118
+ const invK = max(float(1).sub(k), .001).toVar();
119
+ const cyan = float(1).sub(s.r).sub(k).div(invK);
120
+ const mag = float(1).sub(s.g).sub(k).div(invK);
121
+ const yel = float(1).sub(s.b).sub(k).div(invK);
122
+ const dc = dotScreen(coord, cyan, 1.309, uCell);
123
+ const dm = dotScreen(coord, mag, .262, uCell);
124
+ const dy = dotScreen(coord, yel, 0, uCell);
125
+ const dk = dotScreen(coord, k, .785, uCell);
126
+ const outc = clamp(vec3(1).sub(vec3(dc, 0, 0)).sub(vec3(0, dm, 0)).sub(vec3(0, 0, dy)).sub(vec3(dk)), vec3(0), vec3(1));
127
+ return vec4(mix(s.rgb, outc, clamp(uHalftoneCmyk, 0, 1)), s.a);
128
+ })();
129
+ }
130
+ const bayerTable = /*@__PURE__*/ uniformArray([
131
+ 0,
132
+ 32,
133
+ 8,
134
+ 40,
135
+ 2,
136
+ 34,
137
+ 10,
138
+ 42,
139
+ 48,
140
+ 16,
141
+ 56,
142
+ 24,
143
+ 50,
144
+ 18,
145
+ 58,
146
+ 26,
147
+ 12,
148
+ 44,
149
+ 4,
150
+ 36,
151
+ 14,
152
+ 46,
153
+ 6,
154
+ 38,
155
+ 60,
156
+ 28,
157
+ 52,
158
+ 20,
159
+ 62,
160
+ 30,
161
+ 54,
162
+ 22,
163
+ 3,
164
+ 35,
165
+ 11,
166
+ 43,
167
+ 1,
168
+ 33,
169
+ 9,
170
+ 41,
171
+ 51,
172
+ 19,
173
+ 59,
174
+ 27,
175
+ 49,
176
+ 17,
177
+ 57,
178
+ 25,
179
+ 15,
180
+ 47,
181
+ 7,
182
+ 39,
183
+ 13,
184
+ 45,
185
+ 5,
186
+ 37,
187
+ 63,
188
+ 31,
189
+ 55,
190
+ 23,
191
+ 61,
192
+ 29,
193
+ 53,
194
+ 21
195
+ ], "float");
196
+ /** `getBayerValue(uv, 8)` from the GLSL: index the 8x8 screen by the wrapped pixel coordinate. */
197
+ const bayer8 = (p) => {
198
+ const cell = fract(p.div(8)).mul(8).toVar("bayerCell");
199
+ const index = int(cell.y).mul(8).add(int(cell.x));
200
+ return float(bayerTable.element(index)).div(64);
201
+ };
202
+ function dither(sample, u, coord) {
203
+ return Fn(() => {
204
+ const pxSize = max(u.uDitherScale, 1).toVar("dPx");
205
+ const pxSizeUV = coord.div(pxSize).toVar("dPxUv");
206
+ const image = sample(floor(coord.div(pxSize)).add(.5).mul(pxSize).div(max(screenSize, vec2(1)))).toVar("dImage");
207
+ const lum = luma(image.rgb).toVar("dLum");
208
+ const colorSteps = max(floor(u.uDitherSteps), 1).toVar("dSteps");
209
+ const dithering = bayer8(pxSizeUV).sub(.5);
210
+ const brightness = clamp(lum.add(dithering.div(colorSteps)), 0, 1).toVar("dBright");
211
+ brightness.assign(mix(float(0), brightness, image.a));
212
+ const quantLum = floor(brightness.mul(colorSteps).add(.5)).div(colorSteps).toVar("dQuant");
213
+ const color = image.rgb.div(max(lum, .001)).mul(quantLum);
214
+ const quantAlpha = floor(image.a.mul(colorSteps).add(.5)).div(colorSteps);
215
+ return mix(image, vec4(color, mix(quantLum, float(1), quantAlpha)), clamp(u.uDitherStrength, 0, 1));
216
+ })();
217
+ }
218
+ //#endregion
219
+ export { dither, fragCoord, halftone, halftoneCmyk, heatmap, innerLight, paperTexture };
220
+
221
+ //# sourceMappingURL=postEffects.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postEffects.js","names":[],"sources":["../../../src/renderer/tsl/postEffects.ts"],"sourcesContent":["/**\n * The optional post effects, in TSL — ports of the layered passes in `../shaders.ts`.\n *\n * On the WebGL path each of these is a `ShaderPass` with its own render target, so it can sample\n * the previous stage at ANY coordinate (`texture2D(tDiffuse, someOtherUv)`). A node graph has no\n * such target by default: a composed node is an expression evaluated at the current fragment, and\n * cannot be sampled elsewhere. Every effect below that reads an offset coordinate — the light\n * march, the halftone cell centre, the dither block centre — therefore takes a TEXTURE node, and\n * the chain in `postChain.ts` materialises the preceding stage with `convertToTexture()`.\n *\n * Effects that only read the current fragment (heatmap, paper texture, CMYK halftone) take a plain\n * colour node and cost nothing extra.\n */\nimport {\n Fn,\n Loop,\n float,\n int,\n vec2,\n vec3,\n vec4,\n mat2,\n cos,\n sin,\n exp,\n max,\n mix,\n clamp,\n floor,\n fract,\n sqrt,\n dot,\n length,\n smoothstep,\n fwidth,\n uv,\n uniformArray,\n screenSize,\n transpose,\n} from \"three/tsl\";\nimport type { FloatNode, FloatUniform, Vec2Node, Vec2Uniform, Vec3Node, Vec4Node } from \"./types\";\n\n/** A stage that can be sampled at an arbitrary uv (i.e. backed by a render target). */\nexport type Sampler = (at: Vec2Node) => Vec4Node;\n\n/** Rec.709 luminance, as the GLSL `luma`. */\nconst luma = (c: Vec3Node): FloatNode => dot(c, vec3(0.2126, 0.7152, 0.0722));\n\n/**\n * The fragment coordinate in the GLSL convention (origin BOTTOM-left).\n *\n * `screenCoordinate` follows WebGPU's top-left origin and flips Y on WebGL to match, so every\n * effect keyed off `gl_FragCoord` has to flip back or its pattern lands mirrored. Screen-space\n * dot grids and dither matrices are extremely visible when they do.\n */\nexport function fragCoord(screenCoordinate: Vec2Node): Vec2Node {\n return vec2(screenCoordinate.x, screenSize.y.sub(screenCoordinate.y));\n}\n\n// ---- innerLight: radial light scattering (GPU Gems 3 style) ---------------------------------\n\nconst LIGHT_SAMPLES = 24;\n\nexport interface InnerLightUniforms {\n uInnerLight: FloatUniform;\n uInnerLightDensity: FloatUniform;\n uInnerLightDecay: FloatUniform;\n uInnerLightCenter: Vec2Uniform;\n}\n\n/**\n * March from each pixel toward the light point, accumulating the wave's own brightness weighted by\n * alpha (so only opaque pixels emit), then add the streaks back. Runs in the scene zone, so it\n * scatters the raw pre-tone-map wave the way bloom does.\n */\nexport function innerLight(sample: Sampler, u: InnerLightUniforms): Vec4Node {\n return Fn(() => {\n const vUv = uv();\n const src = sample(vUv).toVar(\"ilSrc\");\n const delta = vUv\n .sub(u.uInnerLightCenter)\n .mul(u.uInnerLightDensity.div(LIGHT_SAMPLES))\n .toVar(\"ilDelta\");\n const coord = vUv.toVar(\"ilCoord\");\n const decay = float(1).toVar(\"ilDecay\");\n const rays = vec3(0).toVar(\"ilRays\");\n Loop({ start: 0, end: LIGHT_SAMPLES, type: \"int\" }, () => {\n coord.subAssign(delta);\n const s = sample(coord).toVar();\n rays.addAssign(s.rgb.mul(s.a).mul(decay)); // only opaque (wave) pixels emit light\n decay.mulAssign(u.uInnerLightDecay);\n });\n rays.divAssign(float(LIGHT_SAMPLES));\n const outc = src.rgb.add(rays.mul(u.uInnerLight));\n // Shafts stay visible over a transparent background.\n const outA = max(src.a, luma(rays).mul(u.uInnerLight));\n return vec4(outc, clamp(outA, 0, 1));\n })();\n}\n\n// ---- halftone: rotated dot screen -------------------------------------------------------------\n\nexport interface HalftoneUniforms {\n uHalftone: FloatUniform;\n uHalftoneCell: FloatUniform;\n uHalftoneAngle: FloatUniform;\n}\n\nconst sigmoid = (x: FloatNode, k: number): FloatNode =>\n float(1).div(float(1).add(exp(x.sub(0.5).mul(-k))));\n\n/** paper's classic dot: radius grows as the sampled cell darkens, soft edge via fwidth. */\nconst getCircle = (cellUv: Vec2Node, lum: FloatNode, baseR: number): FloatNode => {\n const r = mix(float(0.25 * baseR), 0.0, lum);\n const d = length(cellUv.sub(0.5)).toVar();\n const aa = fwidth(d);\n return float(1).sub(smoothstep(r.sub(aa), r.add(aa), d));\n};\n\nexport function halftone(sample: Sampler, u: HalftoneUniforms, coord: Vec2Node): Vec4Node {\n return Fn(() => {\n const ca = cos(u.uHalftoneAngle);\n const sa = sin(u.uHalftoneAngle);\n const rot = mat2(ca, sa.negate(), sa, ca).toVar(\"htRot\");\n const cell = max(u.uHalftoneCell, 2.0).toVar(\"htCell\");\n const gridPx = rot.mul(coord).toVar(\"htGrid\"); // rotate the screen into the dot grid\n const cellId = floor(gridPx.div(cell));\n const inCell = fract(gridPx.div(cell)); // position within the cell (0..1)\n const centrePx = transpose(rot).mul(cellId.add(0.5).mul(cell)); // cell centre, back in px\n const tex = sample(centrePx.div(max(screenSize, vec2(1)))).toVar(\"htTex\");\n\n const c = vec3(sigmoid(tex.r, 2), sigmoid(tex.g, 2), sigmoid(tex.b, 2)); // paper default k=2\n const lum = mix(float(1), luma(c), tex.a).toVar(\"htLum\");\n const d = getCircle(inCell, lum, 1.3); // baseR 1.3 ~ paper's original-colours default\n const dots = vec4(tex.rgb, tex.a.mul(d)); // wave-coloured dots, transparent between\n return mix(sample(uv()), dots, clamp(u.uHalftone, 0, 1));\n })();\n}\n\n// ---- heatmap: luminance to a thermal palette ---------------------------------------------------\n\nconst heat = (t: FloatNode): Vec3Node => {\n const s = clamp(t, 0, 1).toVar();\n const c = mix(vec3(0.0, 0.0, 0.4), vec3(0.0, 0.6, 1.0), smoothstep(0.0, 0.25, s)).toVar(\"heatC\");\n c.assign(mix(c, vec3(0.0, 1.0, 0.4), smoothstep(0.25, 0.5, s)));\n c.assign(mix(c, vec3(1.0, 1.0, 0.0), smoothstep(0.5, 0.75, s)));\n c.assign(mix(c, vec3(1.0, 0.1, 0.0), smoothstep(0.75, 1.0, s)));\n return c;\n};\n\nexport function heatmap(src: Vec4Node, uHeatmap: FloatUniform): Vec4Node {\n return Fn(() => {\n const s = src.toVar(\"hmSrc\");\n const l = dot(s.rgb, vec3(0.299, 0.587, 0.114));\n return vec4(mix(s.rgb, heat(l), clamp(uHeatmap, 0, 1)), s.a);\n })();\n}\n\n// ---- paper texture: fibrous substrate shading --------------------------------------------------\n\nconst h21 = (p: Vec2Node): FloatNode => fract(sin(dot(p, vec2(127.1, 311.7))).mul(43758.5453));\n\nexport function paperTexture(\n src: Vec4Node,\n uPaper: FloatUniform,\n uPaperScale: FloatUniform,\n coord: Vec2Node,\n): Vec4Node {\n return Fn(() => {\n const s = src.toVar(\"ptSrc\");\n const p = coord.div(max(uPaperScale, 0.5)).toVar(\"ptP\");\n const fiber = h21(floor(p))\n .mul(0.5)\n .add(h21(floor(p.mul(vec2(0.3, 3.0)))).mul(0.5));\n const tex = mix(fiber, h21(coord), 0.3); // + fine speckle\n const shade = float(1).sub(tex.sub(0.5).mul(0.35));\n return vec4(s.rgb.mul(mix(float(1), shade, clamp(uPaper, 0, 1))), s.a);\n })();\n}\n\n// ---- CMYK halftone: four rotated dot screens ---------------------------------------------------\n\n/** One rotated halftone dot screen for a channel value. */\nconst dotScreen = (\n coord: Vec2Node,\n value: FloatNode,\n angle: number,\n cell: FloatUniform,\n): FloatNode => {\n const ca = Math.cos(angle);\n const sa = Math.sin(angle);\n const r = mat2(ca, -sa, sa, ca).mul(coord);\n const c = fract(r.div(max(cell, 2.0)))\n .sub(0.5)\n .toVar();\n const radius = sqrt(clamp(value, 0, 1))\n .mul(0.5)\n .toVar();\n return smoothstep(radius, radius.sub(0.06), length(c));\n};\n\nexport function halftoneCmyk(\n src: Vec4Node,\n uHalftoneCmyk: FloatUniform,\n uCell: FloatUniform,\n coord: Vec2Node,\n): Vec4Node {\n return Fn(() => {\n const s = src.toVar(\"cmykSrc\");\n const k = float(1)\n .sub(max(max(s.r, s.g), s.b))\n .toVar(\"cmykK\"); // RGB -> CMYK\n const invK = max(float(1).sub(k), 1e-3).toVar();\n const cyan = float(1).sub(s.r).sub(k).div(invK);\n const mag = float(1).sub(s.g).sub(k).div(invK);\n const yel = float(1).sub(s.b).sub(k).div(invK);\n const dc = dotScreen(coord, cyan, 1.309, uCell); // 75 degrees\n const dm = dotScreen(coord, mag, 0.262, uCell); // 15\n const dy = dotScreen(coord, yel, 0.0, uCell); // 0\n const dk = dotScreen(coord, k, 0.785, uCell); // 45\n // Subtractive: cyan absorbs red, magenta green, yellow blue, black all.\n const outc = clamp(\n vec3(1)\n .sub(vec3(dc, 0, 0))\n .sub(vec3(0, dm, 0))\n .sub(vec3(0, 0, dy))\n .sub(vec3(dk)),\n vec3(0),\n vec3(1),\n );\n return vec4(mix(s.rgb, outc, clamp(uHalftoneCmyk, 0, 1)), s.a);\n })();\n}\n\n// ---- dither: ordered (Bayer) ------------------------------------------------------------------\n\nexport interface DitherUniforms {\n uDitherStrength: FloatUniform;\n uDitherScale: FloatUniform;\n uDitherSteps: FloatUniform;\n}\n\n/**\n * paper's default 8x8 ordered screen — the same 64 values the GLSL declares as a `const int[64]`.\n *\n * Kept as the literal table rather than a closed form. The recursive \"magic square\" identities that\n * look like they should reproduce it do not: three plausible bit-interleave formulations were\n * checked against these entries and disagreed on 48 to 56 of the 64, which would have shifted the\n * dither pattern in a way that is easy to mistake for a rendering difference. A uniform array of\n * 64 floats costs one buffer in the post stage, which has room to spare.\n */\nconst BAYER_8X8 = [\n 0, 32, 8, 40, 2, 34, 10, 42, 48, 16, 56, 24, 50, 18, 58, 26, 12, 44, 4, 36, 14, 46, 6, 38, 60, 28,\n 52, 20, 62, 30, 54, 22, 3, 35, 11, 43, 1, 33, 9, 41, 51, 19, 59, 27, 49, 17, 57, 25, 15, 47, 7,\n 39, 13, 45, 5, 37, 63, 31, 55, 23, 61, 29, 53, 21,\n];\n\nconst bayerTable = /*@__PURE__*/ uniformArray(BAYER_8X8, \"float\");\n\n/** `getBayerValue(uv, 8)` from the GLSL: index the 8x8 screen by the wrapped pixel coordinate. */\nconst bayer8 = (p: Vec2Node): FloatNode => {\n const cell = fract(p.div(8)).mul(8).toVar(\"bayerCell\");\n const index = int(cell.y).mul(8).add(int(cell.x));\n return float(bayerTable.element(index as never) as never).div(64.0);\n};\n\nexport function dither(sample: Sampler, u: DitherUniforms, coord: Vec2Node): Vec4Node {\n return Fn(() => {\n const pxSize = max(u.uDitherScale, 1.0).toVar(\"dPx\");\n const pxSizeUV = coord.div(pxSize).toVar(\"dPxUv\");\n const sampleUV = floor(coord.div(pxSize))\n .add(0.5)\n .mul(pxSize)\n .div(max(screenSize, vec2(1)));\n const image = sample(sampleUV).toVar(\"dImage\");\n\n const lum = luma(image.rgb).toVar(\"dLum\");\n const colorSteps = max(floor(u.uDitherSteps), 1.0).toVar(\"dSteps\");\n\n const dithering = bayer8(pxSizeUV).sub(0.5);\n const brightness = clamp(lum.add(dithering.div(colorSteps)), 0, 1).toVar(\"dBright\");\n brightness.assign(mix(float(0), brightness, image.a));\n const quantLum = floor(brightness.mul(colorSteps).add(0.5)).div(colorSteps).toVar(\"dQuant\");\n\n // paper's \"original colours\" path: keep the source hue, quantize luminance.\n const color = image.rgb.div(max(lum, 0.001)).mul(quantLum);\n const quantAlpha = floor(image.a.mul(colorSteps).add(0.5)).div(colorSteps);\n const opacity = mix(quantLum, float(1), quantAlpha);\n\n return mix(image, vec4(color, opacity), clamp(u.uDitherStrength, 0, 1));\n })();\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA8CA,MAAM,QAAQ,MAA2B,IAAI,GAAG,KAAK,OAAQ,OAAQ,KAAM,CAAC;;;;;;;;AAS5E,SAAgB,UAAU,kBAAsC;CAC9D,OAAO,KAAK,iBAAiB,GAAG,WAAW,EAAE,IAAI,iBAAiB,CAAC,CAAC;AACtE;AAIA,MAAM,gBAAgB;;;;;;AActB,SAAgB,WAAW,QAAiB,GAAiC;CAC3E,OAAO,SAAS;EACd,MAAM,MAAM,GAAG;EACf,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,MAAM,OAAO;EACrC,MAAM,QAAQ,IACX,IAAI,EAAE,iBAAiB,CAAC,CACxB,IAAI,EAAE,mBAAmB,IAAI,aAAa,CAAC,CAAC,CAC5C,MAAM,SAAS;EAClB,MAAM,QAAQ,IAAI,MAAM,SAAS;EACjC,MAAM,QAAQ,MAAM,CAAC,CAAC,CAAC,MAAM,SAAS;EACtC,MAAM,OAAO,KAAK,CAAC,CAAC,CAAC,MAAM,QAAQ;EACnC,KAAK;GAAE,OAAO;GAAG,KAAK;GAAe,MAAM;EAAM,SAAS;GACxD,MAAM,UAAU,KAAK;GACrB,MAAM,IAAI,OAAO,KAAK,CAAC,CAAC,MAAM;GAC9B,KAAK,UAAU,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC;GACxC,MAAM,UAAU,EAAE,gBAAgB;EACpC,CAAC;EACD,KAAK,UAAU,MAAM,aAAa,CAAC;EAInC,OAAO,KAHM,IAAI,IAAI,IAAI,KAAK,IAAI,EAAE,WAAW,CAGhC,GAAG,MADL,IAAI,IAAI,GAAG,KAAK,IAAI,CAAC,CAAC,IAAI,EAAE,WAAW,CACzB,GAAG,GAAG,CAAC,CAAC;CACrC,CAAC,CAAC,CAAC;AACL;AAUA,MAAM,WAAW,GAAc,MAC7B,MAAM,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,IAAI,EAAE,IAAI,EAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;;AAGpD,MAAM,aAAa,QAAkB,KAAgB,UAA6B;CAChF,MAAM,IAAI,IAAI,MAAM,MAAO,KAAK,GAAG,GAAK,GAAG;CAC3C,MAAM,IAAI,OAAO,OAAO,IAAI,EAAG,CAAC,CAAC,CAAC,MAAM;CACxC,MAAM,KAAK,OAAO,CAAC;CACnB,OAAO,MAAM,CAAC,CAAC,CAAC,IAAI,WAAW,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;AACzD;AAEA,SAAgB,SAAS,QAAiB,GAAqB,OAA2B;CACxF,OAAO,SAAS;EACd,MAAM,KAAK,IAAI,EAAE,cAAc;EAC/B,MAAM,KAAK,IAAI,EAAE,cAAc;EAC/B,MAAM,MAAM,KAAK,IAAI,GAAG,OAAO,GAAG,IAAI,EAAE,CAAC,CAAC,MAAM,OAAO;EACvD,MAAM,OAAO,IAAI,EAAE,eAAe,CAAG,CAAC,CAAC,MAAM,QAAQ;EACrD,MAAM,SAAS,IAAI,IAAI,KAAK,CAAC,CAAC,MAAM,QAAQ;EAC5C,MAAM,SAAS,MAAM,OAAO,IAAI,IAAI,CAAC;EACrC,MAAM,SAAS,MAAM,OAAO,IAAI,IAAI,CAAC;EAErC,MAAM,MAAM,OADK,UAAU,GAAG,CAAC,CAAC,IAAI,OAAO,IAAI,EAAG,CAAC,CAAC,IAAI,IAAI,CAClC,CAAC,CAAC,IAAI,IAAI,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,OAAO;EAExE,MAAM,IAAI,KAAK,QAAQ,IAAI,GAAG,CAAC,GAAG,QAAQ,IAAI,GAAG,CAAC,GAAG,QAAQ,IAAI,GAAG,CAAC,CAAC;EACtE,MAAM,MAAM,IAAI,MAAM,CAAC,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC,MAAM,OAAO;EACvD,MAAM,IAAI,UAAU,QAAQ,KAAK,GAAG;EACpC,MAAM,OAAO,KAAK,IAAI,KAAK,IAAI,EAAE,IAAI,CAAC,CAAC;EACvC,OAAO,IAAI,OAAO,GAAG,CAAC,GAAG,MAAM,MAAM,EAAE,WAAW,GAAG,CAAC,CAAC;CACzD,CAAC,CAAC,CAAC;AACL;AAIA,MAAM,QAAQ,MAA2B;CACvC,MAAM,IAAI,MAAM,GAAG,GAAG,CAAC,CAAC,CAAC,MAAM;CAC/B,MAAM,IAAI,IAAI,KAAK,GAAK,GAAK,EAAG,GAAG,KAAK,GAAK,IAAK,CAAG,GAAG,WAAW,GAAK,KAAM,CAAC,CAAC,CAAC,CAAC,MAAM,OAAO;CAC/F,EAAE,OAAO,IAAI,GAAG,KAAK,GAAK,GAAK,EAAG,GAAG,WAAW,KAAM,IAAK,CAAC,CAAC,CAAC;CAC9D,EAAE,OAAO,IAAI,GAAG,KAAK,GAAK,GAAK,CAAG,GAAG,WAAW,IAAK,KAAM,CAAC,CAAC,CAAC;CAC9D,EAAE,OAAO,IAAI,GAAG,KAAK,GAAK,IAAK,CAAG,GAAG,WAAW,KAAM,GAAK,CAAC,CAAC,CAAC;CAC9D,OAAO;AACT;AAEA,SAAgB,QAAQ,KAAe,UAAkC;CACvE,OAAO,SAAS;EACd,MAAM,IAAI,IAAI,MAAM,OAAO;EAC3B,MAAM,IAAI,IAAI,EAAE,KAAK,KAAK,MAAO,MAAO,IAAK,CAAC;EAC9C,OAAO,KAAK,IAAI,EAAE,KAAK,KAAK,CAAC,GAAG,MAAM,UAAU,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;CAC7D,CAAC,CAAC,CAAC;AACL;AAIA,MAAM,OAAO,MAA2B,MAAM,IAAI,IAAI,GAAG,KAAK,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC;AAE7F,SAAgB,aACd,KACA,QACA,aACA,OACU;CACV,OAAO,SAAS;EACd,MAAM,IAAI,IAAI,MAAM,OAAO;EAC3B,MAAM,IAAI,MAAM,IAAI,IAAI,aAAa,EAAG,CAAC,CAAC,CAAC,MAAM,KAAK;EAItD,MAAM,MAAM,IAHE,IAAI,MAAM,CAAC,CAAC,CAAC,CACxB,IAAI,EAAG,CAAC,CACR,IAAI,IAAI,MAAM,EAAE,IAAI,KAAK,IAAK,CAAG,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAG,CAC5B,GAAG,IAAI,KAAK,GAAG,EAAG;EACtC,MAAM,QAAQ,MAAM,CAAC,CAAC,CAAC,IAAI,IAAI,IAAI,EAAG,CAAC,CAAC,IAAI,GAAI,CAAC;EACjD,OAAO,KAAK,EAAE,IAAI,IAAI,IAAI,MAAM,CAAC,GAAG,OAAO,MAAM,QAAQ,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAAC;CACvE,CAAC,CAAC,CAAC;AACL;;AAKA,MAAM,aACJ,OACA,OACA,OACA,SACc;CACd,MAAM,KAAK,KAAK,IAAI,KAAK;CACzB,MAAM,KAAK,KAAK,IAAI,KAAK;CAEzB,MAAM,IAAI,MADA,KAAK,IAAI,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,IAAI,KACpB,CAAC,CAAC,IAAI,IAAI,MAAM,CAAG,CAAC,CAAC,CAAC,CACnC,IAAI,EAAG,CAAC,CACR,MAAM;CACT,MAAM,SAAS,KAAK,MAAM,OAAO,GAAG,CAAC,CAAC,CAAC,CACpC,IAAI,EAAG,CAAC,CACR,MAAM;CACT,OAAO,WAAW,QAAQ,OAAO,IAAI,GAAI,GAAG,OAAO,CAAC,CAAC;AACvD;AAEA,SAAgB,aACd,KACA,eACA,OACA,OACU;CACV,OAAO,SAAS;EACd,MAAM,IAAI,IAAI,MAAM,SAAS;EAC7B,MAAM,IAAI,MAAM,CAAC,CAAC,CACf,IAAI,IAAI,IAAI,EAAE,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAC5B,MAAM,OAAO;EAChB,MAAM,OAAO,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC,MAAM;EAC9C,MAAM,OAAO,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI;EAC9C,MAAM,MAAM,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI;EAC7C,MAAM,MAAM,MAAM,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI;EAC7C,MAAM,KAAK,UAAU,OAAO,MAAM,OAAO,KAAK;EAC9C,MAAM,KAAK,UAAU,OAAO,KAAK,MAAO,KAAK;EAC7C,MAAM,KAAK,UAAU,OAAO,KAAK,GAAK,KAAK;EAC3C,MAAM,KAAK,UAAU,OAAO,GAAG,MAAO,KAAK;EAE3C,MAAM,OAAO,MACX,KAAK,CAAC,CAAC,CACJ,IAAI,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,CACnB,IAAI,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CACnB,IAAI,KAAK,GAAG,GAAG,EAAE,CAAC,CAAC,CACnB,IAAI,KAAK,EAAE,CAAC,GACf,KAAK,CAAC,GACN,KAAK,CAAC,CACR;EACA,OAAO,KAAK,IAAI,EAAE,KAAK,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,GAAG,EAAE,CAAC;CAC/D,CAAC,CAAC,CAAC;AACL;AAyBA,MAAM,aAA2B,2BAAa;CAL5C;CAAG;CAAI;CAAG;CAAI;CAAG;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAG;CAAI;CAAI;CAAI;CAAG;CAAI;CAAI;CAC/F;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAG;CAAI;CAAI;CAAI;CAAG;CAAI;CAAG;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAC7F;CAAI;CAAI;CAAI;CAAG;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;CAAI;AAGK,GAAG,OAAO;;AAGhE,MAAM,UAAU,MAA2B;CACzC,MAAM,OAAO,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,WAAW;CACrD,MAAM,QAAQ,IAAI,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,IAAI,KAAK,CAAC,CAAC;CAChD,OAAO,MAAM,WAAW,QAAQ,KAAc,CAAU,CAAC,CAAC,IAAI,EAAI;AACpE;AAEA,SAAgB,OAAO,QAAiB,GAAmB,OAA2B;CACpF,OAAO,SAAS;EACd,MAAM,SAAS,IAAI,EAAE,cAAc,CAAG,CAAC,CAAC,MAAM,KAAK;EACnD,MAAM,WAAW,MAAM,IAAI,MAAM,CAAC,CAAC,MAAM,OAAO;EAKhD,MAAM,QAAQ,OAJG,MAAM,MAAM,IAAI,MAAM,CAAC,CAAC,CACtC,IAAI,EAAG,CAAC,CACR,IAAI,MAAM,CAAC,CACX,IAAI,IAAI,YAAY,KAAK,CAAC,CAAC,CACF,CAAC,CAAC,CAAC,MAAM,QAAQ;EAE7C,MAAM,MAAM,KAAK,MAAM,GAAG,CAAC,CAAC,MAAM,MAAM;EACxC,MAAM,aAAa,IAAI,MAAM,EAAE,YAAY,GAAG,CAAG,CAAC,CAAC,MAAM,QAAQ;EAEjE,MAAM,YAAY,OAAO,QAAQ,CAAC,CAAC,IAAI,EAAG;EAC1C,MAAM,aAAa,MAAM,IAAI,IAAI,UAAU,IAAI,UAAU,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,MAAM,SAAS;EAClF,WAAW,OAAO,IAAI,MAAM,CAAC,GAAG,YAAY,MAAM,CAAC,CAAC;EACpD,MAAM,WAAW,MAAM,WAAW,IAAI,UAAU,CAAC,CAAC,IAAI,EAAG,CAAC,CAAC,CAAC,IAAI,UAAU,CAAC,CAAC,MAAM,QAAQ;EAG1F,MAAM,QAAQ,MAAM,IAAI,IAAI,IAAI,KAAK,IAAK,CAAC,CAAC,CAAC,IAAI,QAAQ;EACzD,MAAM,aAAa,MAAM,MAAM,EAAE,IAAI,UAAU,CAAC,CAAC,IAAI,EAAG,CAAC,CAAC,CAAC,IAAI,UAAU;EAGzE,OAAO,IAAI,OAAO,KAAK,OAFP,IAAI,UAAU,MAAM,CAAC,GAAG,UAEJ,CAAC,GAAG,MAAM,EAAE,iBAAiB,GAAG,CAAC,CAAC;CACxE,CAAC,CAAC,CAAC;AACL"}
@@ -0,0 +1,28 @@
1
+ import { Vector2 } from "three";
2
+ import { uniform } from "three/tsl";
3
+ //#region src/renderer/tsl/types.ts
4
+ /**
5
+ * Node type aliases for the TSL shader graphs.
6
+ *
7
+ * `three/webgpu` is imported for TYPES ONLY here. That import is erased at build time, so this
8
+ * module stays out of the runtime graph — which matters: a single *value* import of `three/webgpu`
9
+ * or `three/tsl` anywhere reachable from the package entry pulls the whole ~200 KB (gzipped) node
10
+ * system into the eager bundle instead of the lazy backend chunk. `src/index.ts` keeps three out
11
+ * the same way.
12
+ */
13
+ /**
14
+ * `floatBitsToUint` / `uintBitsToFloat` are typed as a bare `BitcastNode` in @types/three 0.185,
15
+ * which declares none of the node operators (`.x`, `.sub`, `.toVar`, …) the runtime proxy actually
16
+ * carries. These shims restore the real node type. Delete them once the upstream types catch up —
17
+ * the runtime behaviour is already correct, this is purely a typings gap.
18
+ */
19
+ const asUVec2 = (n) => n;
20
+ const asFloat = (n) => n;
21
+ /** {@link FloatUniform} constructor. */
22
+ const floatUniform = (v) => uniform(v);
23
+ /** {@link Vec2Uniform} constructor. */
24
+ const vec2Uniform = (x, y) => uniform(new Vector2(x, y));
25
+ //#endregion
26
+ export { asFloat, asUVec2, floatUniform, vec2Uniform };
27
+
28
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","names":[],"sources":["../../../src/renderer/tsl/types.ts"],"sourcesContent":["/**\n * Node type aliases for the TSL shader graphs.\n *\n * `three/webgpu` is imported for TYPES ONLY here. That import is erased at build time, so this\n * module stays out of the runtime graph — which matters: a single *value* import of `three/webgpu`\n * or `three/tsl` anywhere reachable from the package entry pulls the whole ~200 KB (gzipped) node\n * system into the eager bundle instead of the lazy backend chunk. `src/index.ts` keeps three out\n * the same way.\n */\nimport { Vector2 } from \"three\";\nimport { uniform } from \"three/tsl\";\nimport type { Node } from \"three/webgpu\";\n\nexport type FloatNode = Node<\"float\">;\nexport type Vec2Node = Node<\"vec2\">;\nexport type Vec3Node = Node<\"vec3\">;\nexport type Vec4Node = Node<\"vec4\">;\nexport type Mat4Node = Node<\"mat4\">;\n\nexport type UVec2Node = Node<\"uvec2\">;\n\n/**\n * `floatBitsToUint` / `uintBitsToFloat` are typed as a bare `BitcastNode` in @types/three 0.185,\n * which declares none of the node operators (`.x`, `.sub`, `.toVar`, …) the runtime proxy actually\n * carries. These shims restore the real node type. Delete them once the upstream types catch up —\n * the runtime behaviour is already correct, this is purely a typings gap.\n */\nexport const asUVec2 = (n: unknown): UVec2Node => n as UVec2Node;\nexport const asFloat = (n: unknown): FloatNode => n as FloatNode;\n\n/**\n * A float uniform with a live `.value`.\n *\n * `uniform()` is declared as `UniformNode<unknown, unknown>`, which carries neither the node\n * operators nor a typed `value`. This narrows both, so call sites read as ordinary node maths and\n * `.value = x` stays type-checked.\n */\nexport type FloatUniform = FloatNode & { value: number };\n\n/** {@link FloatUniform} constructor. */\nexport const floatUniform = (v: number): FloatUniform => uniform(v) as unknown as FloatUniform;\n\n/** A vec2 uniform with a live, mutable `.value`. */\nexport type Vec2Uniform = Vec2Node & { value: Vector2 };\n\n/** {@link Vec2Uniform} constructor. */\nexport const vec2Uniform = (x: number, y: number): Vec2Uniform =>\n uniform(new Vector2(x, y)) as unknown as Vec2Uniform;\n"],"mappings":";;;;;;;;;;;;;;;;;;AA2BA,MAAa,WAAW,MAA0B;AAClD,MAAa,WAAW,MAA0B;;AAYlD,MAAa,gBAAgB,MAA4B,QAAQ,CAAC;;AAMlE,MAAa,eAAe,GAAW,MACrC,QAAQ,IAAI,QAAQ,GAAG,CAAC,CAAC"}
@@ -0,0 +1,152 @@
1
+ import "../../config/model.js";
2
+ import { PackedArrays } from "./packedArray.js";
3
+ import { DataTexture, RGBAFormat, UnsignedByteType, Vector2, Vector3, Vector4 } from "three";
4
+ import { texture, uniform } from "three/tsl";
5
+ //#region src/renderer/tsl/uniforms.ts
6
+ /**
7
+ * The TSL mirror of `WaveRenderer.makeUniforms()`.
8
+ *
9
+ * Every entry keeps the SAME key and the same `.value` write semantics as the GLSL registry, so the
10
+ * ~116 config-sync writes in `refresh()` (`u.uHueShift.value = …`, `u.uColors.value[i].set(…)`) work
11
+ * against either backend without a second code path. That is the whole reason this port is tractable:
12
+ * only the shader graph changes, not the config plumbing around it.
13
+ *
14
+ * Two shapes exist behind that uniform surface:
15
+ * - scalars and vectors ARE the TSL uniform node — it already exposes a live `.value`.
16
+ * - arrays are a `{ value, node }` wrapper, because `UniformArrayNode` keeps the source array on
17
+ * `.array` and uses `.value` for its own padded buffer. The node re-packs from `.array` on every
18
+ * render (`updateType = RENDER`), so mutating the shared elements in place propagates with no
19
+ * explicit invalidation — matching how the GLSL path already mutates its Vector3s.
20
+ */
21
+ /**
22
+ * A 1x1 opaque-white stand-in for the palette texture.
23
+ *
24
+ * The GLSL picks between the palette sample and the procedural gradient with a ternary, which still
25
+ * compiles both sides; the node graph likewise builds the sample unconditionally, so this uniform
26
+ * can never hold `null` the way the GLSL registry's `uPalette` does. `uUsePalette` still decides
27
+ * which result is used — this only guarantees there is something valid to sample.
28
+ */
29
+ function makeFallbackPalette() {
30
+ const tex = new DataTexture(new Uint8Array([
31
+ 255,
32
+ 255,
33
+ 255,
34
+ 255
35
+ ]), 1, 1, RGBAFormat, UnsignedByteType);
36
+ tex.needsUpdate = true;
37
+ return tex;
38
+ }
39
+ /** Build one wave's uniform registry. Defaults mirror `makeUniforms()` exactly. */
40
+ function makeTslUniforms(drawingBufferSize) {
41
+ const packed = new PackedArrays();
42
+ const colorSlots = packed.reserve(8);
43
+ const meshPosSlots = packed.reserve(8);
44
+ const meshColorSlots = packed.reserve(8);
45
+ const lightPosSlots = packed.reserve(8);
46
+ const lightColorSlots = packed.reserve(8);
47
+ const bandBoundsSlots = packed.reserve(4);
48
+ const bandParamsSlots = packed.reserve(4);
49
+ const bandParaSlots = packed.reserve(4);
50
+ const rippleSlots = packed.reserve(4);
51
+ return {
52
+ /** Folds every array view into the shared buffer. Called once per frame before drawing. */
53
+ packed,
54
+ uTime: uniform(0),
55
+ uSpeed: uniform(.05),
56
+ uSeed: uniform(0),
57
+ uDispFreqX: uniform(.003234),
58
+ uDispFreqZ: uniform(.00799),
59
+ uDispAmount: uniform(6.051),
60
+ uDetailFreq: uniform(.04),
61
+ uDetailAmount: uniform(0),
62
+ uTwFreqX: uniform(-.055),
63
+ uTwFreqY: uniform(.077),
64
+ uTwFreqZ: uniform(-.518),
65
+ uTwPowX: uniform(3.95),
66
+ uTwPowY: uniform(5.85),
67
+ uTwPowZ: uniform(6.33),
68
+ uLoopSeconds: uniform(0),
69
+ uColors: packed.vec3(colorSlots, 8, () => new Vector3(1, 1, 1)),
70
+ uColorPos: packed.scalar(colorSlots, 8, (i) => i / 7, "w"),
71
+ uColorCount: uniform(2, "int"),
72
+ uGradType: uniform(0, "int"),
73
+ uGradAngle: uniform(0),
74
+ uGradShift: uniform(.15),
75
+ uMeshPointPos: packed.vec2(meshPosSlots, 8, () => new Vector2(.5, .5)),
76
+ uMeshPointColor: packed.vec3(meshColorSlots, 8, () => new Vector3(1, 1, 1)),
77
+ uMeshPointInfluence: packed.scalar(meshPosSlots, 8, () => .65, "w"),
78
+ uMeshPointCount: uniform(0, "int"),
79
+ uMeshSoftness: uniform(.62),
80
+ uPalette: texture(makeFallbackPalette()),
81
+ uUsePalette: uniform(1),
82
+ uPaletteRaw: uniform(1),
83
+ uPaletteScale: uniform(new Vector2(1, 1)),
84
+ uPaletteOffset: uniform(new Vector2(0, 0)),
85
+ uPaletteRotation: uniform(0),
86
+ uDebug: uniform(0),
87
+ uSheen: uniform(1),
88
+ uRoundness: uniform(.35),
89
+ uIridescence: uniform(0),
90
+ uDepthTint: uniform(0),
91
+ uDepthTintColor: uniform(new Vector3()),
92
+ uHueShift: uniform(0),
93
+ uContrast: uniform(1),
94
+ uSaturation: uniform(1),
95
+ uFiberCount: uniform(90),
96
+ uFiberStrength: uniform(.25),
97
+ uTexture: uniform(0),
98
+ uCreaseLight: uniform(.15),
99
+ uCreaseSharpness: uniform(2),
100
+ uCreaseSoftness: uniform(1),
101
+ uEdgeFade: uniform(.06),
102
+ uEdgeFeather: uniform(.1),
103
+ uOpacity: uniform(1),
104
+ uSquared: uniform(1),
105
+ uResolution: uniform(drawingBufferSize.clone()),
106
+ uAmbient: uniform(.45),
107
+ uNumLights: uniform(1, "int"),
108
+ uLightPos: packed.vec3(lightPosSlots, 8, () => new Vector3()),
109
+ uLightColor: packed.vec3(lightColorSlots, 8, () => new Vector3(1, 1, 1)),
110
+ uLightIntensity: packed.scalar(lightPosSlots, 8, () => 0, "w"),
111
+ uNumNoiseBands: uniform(0, "int"),
112
+ uNoiseBandBounds: packed.vec4(bandBoundsSlots, 4, () => new Vector4()),
113
+ uNoiseBandParams: packed.vec4(bandParamsSlots, 4, () => new Vector4()),
114
+ uNoiseBandParaPow: packed.scalar(bandParaSlots, 4, () => 0, "x"),
115
+ uLineAmount: uniform(425),
116
+ uLineThickness: uniform(1),
117
+ uLineDerivativePower: uniform(.95),
118
+ uMaxWidth: uniform(1232),
119
+ uClearColor: uniform(new Vector3(1, 1, 1)),
120
+ uHelixTurns: uniform(0),
121
+ uHelixRadius: uniform(0),
122
+ uHelixRoll: uniform(0),
123
+ uHelixPhase: uniform(0),
124
+ uRadialAmount: uniform(0),
125
+ uRadialArc: uniform(160),
126
+ uRadialSpread: uniform(1),
127
+ uRadialRadius: uniform(40),
128
+ uRadialCenter: uniform(0),
129
+ uRungAmount: uniform(0),
130
+ uRungThickness: uniform(1),
131
+ uPointer: uniform(new Vector2(0, 0)),
132
+ uPointerActive: uniform(0),
133
+ uPointerRadius: uniform(.6),
134
+ uPointerAspect: uniform(1),
135
+ uPointerAgitate: uniform(0),
136
+ uPointerPush: uniform(0),
137
+ uPointerWake: uniform(0),
138
+ uPointerVel: uniform(new Vector2(0, 0)),
139
+ uShapeFlow: uniform(0),
140
+ uPointerThin: uniform(0),
141
+ uPointerHue: uniform(0),
142
+ uPointerLighten: uniform(0),
143
+ uPointerRipple: uniform(0),
144
+ uRippleOrigin: packed.vec2(rippleSlots, 4, () => new Vector2()),
145
+ uRippleAge: packed.scalar(rippleSlots, 4, () => 0, "z"),
146
+ uRippleAmp: packed.scalar(rippleSlots, 4, () => 0, "w")
147
+ };
148
+ }
149
+ //#endregion
150
+ export { makeTslUniforms };
151
+
152
+ //# sourceMappingURL=uniforms.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"uniforms.js","names":[],"sources":["../../../src/renderer/tsl/uniforms.ts"],"sourcesContent":["/**\n * The TSL mirror of `WaveRenderer.makeUniforms()`.\n *\n * Every entry keeps the SAME key and the same `.value` write semantics as the GLSL registry, so the\n * ~116 config-sync writes in `refresh()` (`u.uHueShift.value = …`, `u.uColors.value[i].set(…)`) work\n * against either backend without a second code path. That is the whole reason this port is tractable:\n * only the shader graph changes, not the config plumbing around it.\n *\n * Two shapes exist behind that uniform surface:\n * - scalars and vectors ARE the TSL uniform node — it already exposes a live `.value`.\n * - arrays are a `{ value, node }` wrapper, because `UniformArrayNode` keeps the source array on\n * `.array` and uses `.value` for its own padded buffer. The node re-packs from `.array` on every\n * render (`updateType = RENDER`), so mutating the shared elements in place propagates with no\n * explicit invalidation — matching how the GLSL path already mutates its Vector3s.\n */\nimport { DataTexture, RGBAFormat, UnsignedByteType, Vector2, Vector3, Vector4 } from \"three\";\nimport { uniform, texture } from \"three/tsl\";\nimport { PackedArrays } from \"./packedArray\";\nimport { MAX_COLORS, MAX_LIGHTS, MAX_MESH_POINTS, MAX_NOISE_BANDS } from \"../../config/model\";\nimport { RIPPLE_SLOTS } from \"../interactionGates\";\n\n/**\n * A 1x1 opaque-white stand-in for the palette texture.\n *\n * The GLSL picks between the palette sample and the procedural gradient with a ternary, which still\n * compiles both sides; the node graph likewise builds the sample unconditionally, so this uniform\n * can never hold `null` the way the GLSL registry's `uPalette` does. `uUsePalette` still decides\n * which result is used — this only guarantees there is something valid to sample.\n */\nfunction makeFallbackPalette(): DataTexture {\n const tex = new DataTexture(\n new Uint8Array([255, 255, 255, 255]),\n 1,\n 1,\n RGBAFormat,\n UnsignedByteType,\n );\n tex.needsUpdate = true;\n return tex;\n}\n\n/** Build one wave's uniform registry. Defaults mirror `makeUniforms()` exactly. */\nexport function makeTslUniforms(drawingBufferSize: Vector2) {\n // Every array-valued uniform shares ONE buffer — see PackedArrays for why (WebGPU allows only 12\n // uniform buffers per shader stage, and TSL gives each uniformArray its own).\n const packed = new PackedArrays();\n const colorSlots = packed.reserve(MAX_COLORS);\n const meshPosSlots = packed.reserve(MAX_MESH_POINTS);\n const meshColorSlots = packed.reserve(MAX_MESH_POINTS);\n const lightPosSlots = packed.reserve(MAX_LIGHTS);\n const lightColorSlots = packed.reserve(MAX_LIGHTS);\n const bandBoundsSlots = packed.reserve(MAX_NOISE_BANDS);\n const bandParamsSlots = packed.reserve(MAX_NOISE_BANDS);\n const bandParaSlots = packed.reserve(MAX_NOISE_BANDS);\n const rippleSlots = packed.reserve(RIPPLE_SLOTS);\n\n return {\n /** Folds every array view into the shared buffer. Called once per frame before drawing. */\n packed,\n // ---- Deformation (vertex) ----\n uTime: uniform(0),\n uSpeed: uniform(0.05),\n uSeed: uniform(0),\n uDispFreqX: uniform(0.003234),\n uDispFreqZ: uniform(0.00799),\n uDispAmount: uniform(6.051),\n uDetailFreq: uniform(0.04),\n uDetailAmount: uniform(0),\n uTwFreqX: uniform(-0.055),\n uTwFreqY: uniform(0.077),\n uTwFreqZ: uniform(-0.518),\n uTwPowX: uniform(3.95),\n uTwPowY: uniform(5.85),\n uTwPowZ: uniform(6.33),\n uLoopSeconds: uniform(0),\n\n // ---- Colour + light (fragment) ----\n uColors: packed.vec3(colorSlots, MAX_COLORS, () => new Vector3(1, 1, 1)),\n uColorPos: packed.scalar(\n colorSlots,\n MAX_COLORS,\n (i) => (MAX_COLORS > 1 ? i / (MAX_COLORS - 1) : 0),\n \"w\",\n ),\n uColorCount: uniform(2, \"int\"),\n uGradType: uniform(0, \"int\"),\n uGradAngle: uniform(0),\n uGradShift: uniform(0.15),\n uMeshPointPos: packed.vec2(meshPosSlots, MAX_MESH_POINTS, () => new Vector2(0.5, 0.5)),\n uMeshPointColor: packed.vec3(meshColorSlots, MAX_MESH_POINTS, () => new Vector3(1, 1, 1)),\n uMeshPointInfluence: packed.scalar(meshPosSlots, MAX_MESH_POINTS, () => 0.65, \"w\"),\n uMeshPointCount: uniform(0, \"int\"),\n uMeshSoftness: uniform(0.62),\n uPalette: texture(makeFallbackPalette()),\n uUsePalette: uniform(1),\n uPaletteRaw: uniform(1),\n uPaletteScale: uniform(new Vector2(1, 1)),\n uPaletteOffset: uniform(new Vector2(0, 0)),\n uPaletteRotation: uniform(0),\n uDebug: uniform(0),\n uSheen: uniform(1),\n uRoundness: uniform(0.35),\n uIridescence: uniform(0),\n uDepthTint: uniform(0),\n uDepthTintColor: uniform(new Vector3()),\n uHueShift: uniform(0),\n uContrast: uniform(1),\n uSaturation: uniform(1),\n uFiberCount: uniform(90),\n uFiberStrength: uniform(0.25),\n uTexture: uniform(0),\n uCreaseLight: uniform(0.15),\n uCreaseSharpness: uniform(2.0),\n uCreaseSoftness: uniform(1.0),\n uEdgeFade: uniform(0.06),\n uEdgeFeather: uniform(0.1),\n uOpacity: uniform(1),\n uSquared: uniform(1),\n // Seeded from the CURRENT drawing buffer, not (1,1) — see the note in makeUniforms(): the\n // edgeFade vignette divides gl_FragCoord by this, so a stale (1,1) renders the wave invisible.\n uResolution: uniform(drawingBufferSize.clone()),\n uAmbient: uniform(0.45),\n uNumLights: uniform(1, \"int\"),\n uLightPos: packed.vec3(lightPosSlots, MAX_LIGHTS, () => new Vector3()),\n uLightColor: packed.vec3(lightColorSlots, MAX_LIGHTS, () => new Vector3(1, 1, 1)),\n uLightIntensity: packed.scalar(lightPosSlots, MAX_LIGHTS, () => 0, \"w\"),\n uNumNoiseBands: uniform(0, \"int\"),\n uNoiseBandBounds: packed.vec4(bandBoundsSlots, MAX_NOISE_BANDS, () => new Vector4()),\n uNoiseBandParams: packed.vec4(bandParamsSlots, MAX_NOISE_BANDS, () => new Vector4()),\n uNoiseBandParaPow: packed.scalar(bandParaSlots, MAX_NOISE_BANDS, () => 0, \"x\"),\n\n // ---- Wireframe thin-line theme ----\n uLineAmount: uniform(425),\n uLineThickness: uniform(1),\n uLineDerivativePower: uniform(0.95),\n uMaxWidth: uniform(1232),\n uClearColor: uniform(new Vector3(1, 1, 1)),\n\n // ---- Helix / radial / rungs ----\n uHelixTurns: uniform(0),\n uHelixRadius: uniform(0),\n uHelixRoll: uniform(0),\n uHelixPhase: uniform(0),\n uRadialAmount: uniform(0),\n uRadialArc: uniform(160),\n uRadialSpread: uniform(1),\n uRadialRadius: uniform(40),\n uRadialCenter: uniform(0),\n uRungAmount: uniform(0),\n uRungThickness: uniform(1),\n\n // ---- Interaction / pointer field ----\n uPointer: uniform(new Vector2(0, 0)),\n uPointerActive: uniform(0),\n uPointerRadius: uniform(0.6),\n uPointerAspect: uniform(1),\n uPointerAgitate: uniform(0),\n uPointerPush: uniform(0),\n uPointerWake: uniform(0),\n uPointerVel: uniform(new Vector2(0, 0)),\n uShapeFlow: uniform(0),\n uPointerThin: uniform(0),\n uPointerHue: uniform(0),\n uPointerLighten: uniform(0),\n uPointerRipple: uniform(0),\n uRippleOrigin: packed.vec2(rippleSlots, RIPPLE_SLOTS, () => new Vector2()),\n uRippleAge: packed.scalar(rippleSlots, RIPPLE_SLOTS, () => 0, \"z\"),\n uRippleAmp: packed.scalar(rippleSlots, RIPPLE_SLOTS, () => 0, \"w\"),\n };\n}\n\nexport type WaveTslUniforms = ReturnType<typeof makeTslUniforms>;\n"],"mappings":"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"}
@@ -0,0 +1,185 @@
1
+ import "../../config/model.js";
2
+ import { grainHash, simplexNoise } from "./noise.js";
3
+ import { applyTwist, waveShape } from "./waveShape.js";
4
+ import { pointerField } from "./pointerField.js";
5
+ import { applyColorGrade, hueShift, mapLinear, parabola, waveBaseColor } from "./color.js";
6
+ import { NodeMaterial } from "three/webgpu";
7
+ import { Break, Fn, If, Loop, abs, cameraPosition, cameraProjectionMatrix, cameraViewMatrix, clamp, cos, cross, dFdx, dFdy, dot, float, fwidth, max, mix, modelWorldMatrix, normalize, positionLocal, positionWorld, pow, radians, screenUV, select, sin, smoothstep, uv, varying, vec2, vec3, vec4 } from "three/tsl";
8
+ //#region src/renderer/tsl/waveMaterial.ts
9
+ /**
10
+ * The wave's `NodeMaterial` — the TSL port of `vertexShader` + `fragmentShader` / `lineFragmentShader`.
11
+ *
12
+ * The GLSL `#ifdef` variants become JS flags, so each material builds exactly the graph it needs
13
+ * instead of relying on a define set and a program cache key.
14
+ *
15
+ * Two deliberate differences from the GLSL, both because the node pipeline already does the work:
16
+ * - No `PREMULTIPLIED_ALPHA` block. `NodeMaterial.setupOutput()` calls `setupPremultipliedAlpha()`
17
+ * when `material.premultipliedAlpha` is set, so premultiplying here would double-apply it.
18
+ * - No output colour-space conversion. The material writes LINEAR, exactly as the GLSL does; the
19
+ * conversion happens once at the end of the post chain, matching `OutputPass` on the WebGL path.
20
+ */
21
+ /** Linear + orbit time. Only the one selected by `loopMotion` is read, as in the GLSL. */
22
+ function timeNodes(u, flags) {
23
+ if (!flags.loopMotion) return {
24
+ t: u.uTime.mul(u.uSpeed).add(u.uSeed),
25
+ loopOff: vec2(0, 0)
26
+ };
27
+ const loopTheta = u.uTime.mul(float(6.28318530718).div(u.uLoopSeconds)).add(u.uSeed).toVar();
28
+ const loopR = u.uSpeed.mul(u.uLoopSeconds).mul(.159154943092);
29
+ return {
30
+ t: float(0),
31
+ loopOff: vec2(cos(loopTheta), sin(loopTheta)).mul(loopR)
32
+ };
33
+ }
34
+ /**
35
+ * Striations: subtle high-frequency simplex grain ADDED to the colour — colour-matched (weaker
36
+ * where blue is high), only near folds, and concentrated toward the ends. Blends rather than
37
+ * reading as hard lines. Noise bands override the params inside rectangular uv regions.
38
+ */
39
+ function surfaceStreaks(u, uvIn, color, crease) {
40
+ const strength = float(u.uFiberStrength).toVar("streakStrength");
41
+ const freq = float(u.uFiberCount).toVar("streakFreq");
42
+ const colorAtten = float(.9).toVar("streakAtten");
43
+ const paraPow = float(3).toVar("streakPara");
44
+ Loop({
45
+ start: 0,
46
+ end: 4,
47
+ type: "int"
48
+ }, ({ i }) => {
49
+ If(float(i).greaterThanEqual(float(u.uNumNoiseBands)), () => {
50
+ Break();
51
+ });
52
+ const b = u.uNoiseBandBounds.el(i).toVar();
53
+ const prm = u.uNoiseBandParams.el(i).toVar();
54
+ const feather = max(prm.x, 1e-4).toVar();
55
+ const blend = smoothstep(b.x.sub(feather), b.x, uvIn.x).mul(float(1).sub(smoothstep(b.y, b.y.add(feather), uvIn.x))).mul(smoothstep(b.z.sub(feather), b.z, uvIn.y)).mul(float(1).sub(smoothstep(b.w, b.w.add(feather), uvIn.y)));
56
+ strength.assign(mix(strength, prm.y, blend));
57
+ freq.assign(mix(freq, prm.z, blend));
58
+ colorAtten.assign(mix(colorAtten, prm.w, blend));
59
+ paraPow.assign(mix(paraPow, u.uNoiseBandParaPow.el(i), blend));
60
+ });
61
+ const p = float(1).sub(parabola(uvIn.x, paraPow));
62
+ const n0 = simplexNoise(vec2(uvIn.x.mul(.1), uvIn.y.mul(.5))).toVar();
63
+ const n1 = mapLinear(simplexNoise(vec2(uvIn.x.mul(freq.add(freq.mul(.5).mul(n0))), uvIn.y.mul(4).mul(n0))), -1, 1, 0, 1);
64
+ return color.add(n1.mul(strength).mul(float(1).sub(color.b.mul(colorAtten))).mul(crease).mul(p));
65
+ }
66
+ /** Build one wave's material for the given flags. */
67
+ function buildWaveMaterial(u, flags) {
68
+ const material = new NodeMaterial();
69
+ material.transparent = true;
70
+ material.depthTest = true;
71
+ material.depthWrite = true;
72
+ material.side = 2;
73
+ let pointerFall = null;
74
+ material.positionNode = Fn(() => {
75
+ const { t, loopOff } = timeNodes(u, flags);
76
+ const ws = waveShape(u, flags, positionLocal, uv(), t, loopOff);
77
+ if (!flags.pointerFx) return ws.pos;
78
+ const mvp = cameraProjectionMatrix.mul(cameraViewMatrix).mul(modelWorldMatrix).toVar("mvp");
79
+ const preClip = mvp.mul(vec4(ws.pos, 1)).toVar("preClip");
80
+ const hit = pointerField(u, {
81
+ loopMotion: flags.loopMotion,
82
+ ripples: flags.pointerRipples
83
+ }, preClip.xy.div(max(preClip.w, 1e-6)), mvp, ws.twists, ws.pos, t, loopOff);
84
+ pointerFall = varying(hit.fall, "vPointerFall");
85
+ const dispAxis = applyTwist(applyTwist(applyTwist(vec3(0, 1, 0), ws.twists[0]), ws.twists[1]), ws.twists[2]);
86
+ return ws.pos.add(dispAxis.mul(hit.disp));
87
+ })();
88
+ const rawClip = varying(cameraProjectionMatrix.mul(cameraViewMatrix).mul(modelWorldMatrix).mul(vec4(material.positionNode, 1)), "vClipPosition");
89
+ const clipZ = flags.webgpuClipZ ? rawClip.z.mul(2).sub(1) : rawClip.z;
90
+ material.outputNode = flags.theme === "wireframe" ? buildWireframeFragment(u, flags, clipZ, pointerFall) : buildSolidFragment(u, flags, clipZ, pointerFall);
91
+ return material;
92
+ }
93
+ /** The wireframe thin-line theme: colour carved into fine lengthwise strands, faded by depth. */
94
+ function buildWireframeFragment(u, flags, clipZ, pointerFall) {
95
+ return Fn(() => {
96
+ const vUv = uv();
97
+ const color = applyColorGrade(u, waveBaseColor(u, vUv)).toVar("lineColor");
98
+ if (pointerFall) {
99
+ color.assign(hueShift(color, radians(u.uPointerHue).mul(pointerFall)));
100
+ color.mulAssign(float(1).add(u.uPointerLighten.mul(pointerFall)));
101
+ }
102
+ const dy = dFdy(vUv).toVar();
103
+ const lineThickness = u.uLineThickness.mul(pow(abs(dy.x.mul(u.uMaxWidth)), u.uLineDerivativePower)).toVar("lineThickness");
104
+ if (pointerFall) lineThickness.mulAssign(clamp(float(1).sub(u.uPointerThin.mul(pointerFall)), 0, 1));
105
+ const a = smoothstep(lineThickness, 0, abs(sin(vUv.x.mul(u.uLineAmount)))).toVar("lineA");
106
+ if (flags.rungs) {
107
+ const rung = abs(sin(vUv.y.mul(u.uRungAmount)));
108
+ a.assign(max(a, smoothstep(u.uRungThickness.mul(u.uRungAmount).mul(fwidth(vUv.y)), 0, rung)));
109
+ }
110
+ const depthFade = clamp(clipZ.mul(6), 0, 1);
111
+ const faded = mix(u.uClearColor, color, a.mul(float(1).sub(depthFade))).toVar("lineFaded");
112
+ return vec4(select(u.uSquared.greaterThan(.5), faded.mul(faded), faded), u.uOpacity);
113
+ })();
114
+ }
115
+ /** The solid surface theme. */
116
+ function buildSolidFragment(u, flags, clipZ, pointerFall) {
117
+ return Fn(() => {
118
+ const vUv = uv();
119
+ const vWorldPos = positionWorld;
120
+ const vViewDir = cameraPosition.sub(vWorldPos).toVar("vViewDir");
121
+ const crease = float(0).toVar("crease");
122
+ crease.assign(dFdy(vUv).y.mul(u.uResolution.y).mul(u.uCreaseLight));
123
+ crease.assign(clamp(mapLinear(crease, -1, 1, 0, 1), 0, 1));
124
+ crease.assign(pow(crease, u.uCreaseSharpness));
125
+ crease.assign(clamp(smoothstep(0, u.uCreaseSoftness, crease), 0, 1));
126
+ const col = waveBaseColor(u, vUv).toVar("col");
127
+ col.assign(surfaceStreaks(u, vUv, col, crease));
128
+ col.assign(applyColorGrade(u, col));
129
+ if (pointerFall) {
130
+ col.assign(hueShift(col, radians(u.uPointerHue).mul(pointerFall)));
131
+ col.mulAssign(float(1).add(u.uPointerLighten.mul(pointerFall)));
132
+ }
133
+ If(u.uIridescence.greaterThan(.001), () => {
134
+ const iridFacing = abs(dot(normalize(cross(dFdx(vWorldPos), dFdy(vWorldPos))), normalize(vViewDir)));
135
+ col.assign(hueShift(col, float(1).sub(iridFacing).mul(u.uIridescence).mul(Math.PI)));
136
+ });
137
+ col.addAssign(float(1).sub(crease).mul(.25).mul(u.uSheen));
138
+ If(u.uRoundness.greaterThan(.001), () => {
139
+ const facing = abs(dot(normalize(cross(dFdx(vWorldPos), dFdy(vWorldPos))), normalize(vViewDir))).toVar("facing");
140
+ col.mulAssign(mix(float(1).sub(u.uRoundness.mul(.6)), 1, facing));
141
+ col.addAssign(smoothstep(.65, 1, facing).mul(u.uRoundness).mul(.18));
142
+ });
143
+ If(u.uNumLights.greaterThan(0), () => {
144
+ const n = normalize(cross(dFdx(vWorldPos), dFdy(vWorldPos))).toVar("lightN");
145
+ const vd = normalize(vViewDir).toVar("lightV");
146
+ If(dot(n, vd).lessThan(0), () => {
147
+ n.assign(n.negate());
148
+ });
149
+ Loop({
150
+ start: 0,
151
+ end: 8,
152
+ type: "int"
153
+ }, ({ i }) => {
154
+ If(float(i).greaterThanEqual(float(u.uNumLights)), () => {
155
+ Break();
156
+ });
157
+ const l = normalize(u.uLightPos.el(i).sub(vWorldPos)).toVar();
158
+ const lc = u.uLightColor.el(i).mul(u.uLightIntensity.el(i)).toVar();
159
+ const diff = max(dot(n, l), 0);
160
+ const spec = pow(max(dot(n, normalize(l.add(vd))), 0), 28);
161
+ col.addAssign(col.mul(diff).mul(lc).mul(.16).add(lc.mul(spec).mul(.1)));
162
+ });
163
+ });
164
+ col.mulAssign(clamp(u.uAmbient, 0, 1).add(.55));
165
+ if (flags.depthTint) col.assign(mix(col, u.uDepthTintColor, clamp(clipZ.mul(6), 0, 1).mul(u.uDepthTint)));
166
+ If(u.uTexture.greaterThan(.001), () => {
167
+ col.mulAssign(float(1).add(grainHash(vUv.mul(850)).sub(.5).mul(u.uTexture).mul(.25)));
168
+ });
169
+ const ribEdge = flags.edgeFeather ? smoothstep(0, u.uEdgeFeather, vUv.y).mul(float(1).sub(smoothstep(float(1).sub(u.uEdgeFeather), 1, vUv.y))) : smoothstep(0, .1, vUv.y).mul(float(1).sub(smoothstep(.9, 1, vUv.y)));
170
+ const alpha = u.uOpacity.mul(ribEdge).toVar("alpha");
171
+ if (pointerFall) alpha.mulAssign(clamp(float(1).sub(u.uPointerThin.mul(pointerFall)), 0, 1));
172
+ If(u.uEdgeFade.greaterThan(.001), () => {
173
+ const sc = screenUV;
174
+ const vig = smoothstep(0, u.uEdgeFade, sc.x).mul(float(1).sub(smoothstep(float(1).sub(u.uEdgeFade), 1, sc.x))).mul(smoothstep(0, u.uEdgeFade, sc.y)).mul(float(1).sub(smoothstep(float(1).sub(u.uEdgeFade), 1, sc.y)));
175
+ alpha.mulAssign(vig);
176
+ });
177
+ col.assign(clamp(col, vec3(0), vec3(1)));
178
+ const squared = u.uSquared.greaterThan(.5);
179
+ return vec4(select(squared, col.mul(col), col), select(squared, alpha.mul(alpha), alpha));
180
+ })();
181
+ }
182
+ //#endregion
183
+ export { buildWaveMaterial };
184
+
185
+ //# sourceMappingURL=waveMaterial.js.map