@holoscript/engine 6.0.3 → 6.0.4

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 (192) hide show
  1. package/dist/AutoMesher-CK47F6AV.js +17 -0
  2. package/dist/GPUBuffers-2LHBCD7X.js +9 -0
  3. package/dist/WebGPUContext-TNEUYU2Y.js +11 -0
  4. package/dist/animation/index.cjs +38 -38
  5. package/dist/animation/index.d.cts +1 -1
  6. package/dist/animation/index.d.ts +1 -1
  7. package/dist/animation/index.js +1 -1
  8. package/dist/audio/index.cjs +16 -6
  9. package/dist/audio/index.d.cts +1 -1
  10. package/dist/audio/index.d.ts +1 -1
  11. package/dist/audio/index.js +1 -1
  12. package/dist/camera/index.cjs +23 -23
  13. package/dist/camera/index.d.cts +1 -1
  14. package/dist/camera/index.d.ts +1 -1
  15. package/dist/camera/index.js +1 -1
  16. package/dist/character/index.cjs +6 -4
  17. package/dist/character/index.js +1 -1
  18. package/dist/choreography/index.cjs +1194 -0
  19. package/dist/choreography/index.d.cts +687 -0
  20. package/dist/choreography/index.d.ts +687 -0
  21. package/dist/choreography/index.js +1156 -0
  22. package/dist/chunk-2CSNRI2N.js +217 -0
  23. package/dist/chunk-33T2WINR.js +266 -0
  24. package/dist/chunk-35R73OFM.js +1257 -0
  25. package/dist/chunk-4MMDSUNP.js +1256 -0
  26. package/dist/chunk-5V6HOU72.js +319 -0
  27. package/dist/chunk-6QOP6PYF.js +1038 -0
  28. package/dist/chunk-7KMJVHIL.js +8944 -0
  29. package/dist/chunk-7VPUC62U.js +1106 -0
  30. package/dist/chunk-A2Y6RCAT.js +1878 -0
  31. package/dist/chunk-AHM42MK6.js +8944 -0
  32. package/dist/chunk-BL7IDTHE.js +218 -0
  33. package/dist/chunk-CITOMSWL.js +10462 -0
  34. package/dist/chunk-CXDPKW2K.js +8944 -0
  35. package/dist/chunk-CXZPLD4S.js +223 -0
  36. package/dist/chunk-CZYJE7IH.js +5169 -0
  37. package/dist/chunk-D2OP7YC7.js +6325 -0
  38. package/dist/chunk-EDRVQHUU.js +1544 -0
  39. package/dist/chunk-EJSLOOW2.js +3589 -0
  40. package/dist/chunk-F53SFGW5.js +1878 -0
  41. package/dist/chunk-HCFPELPY.js +919 -0
  42. package/dist/chunk-HNEE36PY.js +93 -0
  43. package/dist/chunk-HYXNV36F.js +1256 -0
  44. package/dist/chunk-IB7KHVFY.js +821 -0
  45. package/dist/chunk-IBBO7YYG.js +690 -0
  46. package/dist/chunk-ILIBGINU.js +5470 -0
  47. package/dist/chunk-IS4MHLKN.js +5479 -0
  48. package/dist/chunk-JT2PFKWD.js +5479 -0
  49. package/dist/chunk-K4CUB4NY.js +1038 -0
  50. package/dist/chunk-KATDQXRJ.js +10462 -0
  51. package/dist/chunk-KBQE6ZFJ.js +8944 -0
  52. package/dist/chunk-KBVD5K7E.js +560 -0
  53. package/dist/chunk-KCDPVQRY.js +4088 -0
  54. package/dist/chunk-KN4QJPKN.js +8944 -0
  55. package/dist/chunk-KWJ3ROSI.js +8944 -0
  56. package/dist/chunk-L45VF6DD.js +919 -0
  57. package/dist/chunk-LY4T37YK.js +307 -0
  58. package/dist/chunk-MDN5WZXA.js +1544 -0
  59. package/dist/chunk-MGCDP6VU.js +928 -0
  60. package/dist/chunk-NCX7X6G2.js +8681 -0
  61. package/dist/chunk-OF54BPVD.js +913 -0
  62. package/dist/chunk-OWSN2Q3Q.js +690 -0
  63. package/dist/chunk-PRRB5TTA.js +406 -0
  64. package/dist/chunk-PXWVQF76.js +4086 -0
  65. package/dist/chunk-PYCOIDT2.js +812 -0
  66. package/dist/chunk-PZCSADOV.js +928 -0
  67. package/dist/chunk-Q2XBVS2K.js +1038 -0
  68. package/dist/chunk-QDZRXWN5.js +1776 -0
  69. package/dist/chunk-RNWOZ6WQ.js +913 -0
  70. package/dist/chunk-ROLFT4CJ.js +1693 -0
  71. package/dist/chunk-SLTJRZ2N.js +266 -0
  72. package/dist/chunk-SRUS5XSU.js +4088 -0
  73. package/dist/chunk-TKCA3WZ5.js +5409 -0
  74. package/dist/chunk-TNRMXYI2.js +1650 -0
  75. package/dist/chunk-TQB3GJGM.js +9763 -0
  76. package/dist/chunk-TUFGXG6K.js +510 -0
  77. package/dist/chunk-U6KMTGQJ.js +632 -0
  78. package/dist/chunk-VMGJQST6.js +8681 -0
  79. package/dist/chunk-X4F4TCG4.js +5470 -0
  80. package/dist/chunk-ZIFROE75.js +1544 -0
  81. package/dist/chunk-ZIJQYHSQ.js +1204 -0
  82. package/dist/combat/index.cjs +4 -4
  83. package/dist/combat/index.d.cts +1 -1
  84. package/dist/combat/index.d.ts +1 -1
  85. package/dist/combat/index.js +1 -1
  86. package/dist/ecs/index.cjs +1 -1
  87. package/dist/ecs/index.js +1 -1
  88. package/dist/environment/index.cjs +14 -14
  89. package/dist/environment/index.d.cts +1 -1
  90. package/dist/environment/index.d.ts +1 -1
  91. package/dist/environment/index.js +1 -1
  92. package/dist/gpu/index.cjs +4810 -0
  93. package/dist/gpu/index.js +3714 -0
  94. package/dist/hologram/index.cjs +27 -1
  95. package/dist/hologram/index.js +1 -1
  96. package/dist/index-B2PIsAmR.d.cts +2180 -0
  97. package/dist/index-B2PIsAmR.d.ts +2180 -0
  98. package/dist/index-BHySEPX7.d.cts +2921 -0
  99. package/dist/index-BJV21zuy.d.cts +341 -0
  100. package/dist/index-BJV21zuy.d.ts +341 -0
  101. package/dist/index-BQutTphC.d.cts +790 -0
  102. package/dist/index-ByIq2XrS.d.cts +3910 -0
  103. package/dist/index-BysHjDSO.d.cts +224 -0
  104. package/dist/index-BysHjDSO.d.ts +224 -0
  105. package/dist/index-CKwAJGck.d.ts +455 -0
  106. package/dist/index-CUl3QstQ.d.cts +3006 -0
  107. package/dist/index-CUl3QstQ.d.ts +3006 -0
  108. package/dist/index-CmYtNiI-.d.cts +953 -0
  109. package/dist/index-CmYtNiI-.d.ts +953 -0
  110. package/dist/index-CnRzWxi_.d.cts +522 -0
  111. package/dist/index-CnRzWxi_.d.ts +522 -0
  112. package/dist/index-CwRWbSC7.d.ts +2921 -0
  113. package/dist/index-CxKIBstO.d.ts +790 -0
  114. package/dist/index-DJ6-R8vh.d.cts +455 -0
  115. package/dist/index-DQKisbcI.d.cts +4968 -0
  116. package/dist/index-DQKisbcI.d.ts +4968 -0
  117. package/dist/index-DRT2zJez.d.ts +3910 -0
  118. package/dist/index-DfNLiAka.d.cts +192 -0
  119. package/dist/index-DfNLiAka.d.ts +192 -0
  120. package/dist/index-nMvkoRm8.d.cts +405 -0
  121. package/dist/index-nMvkoRm8.d.ts +405 -0
  122. package/dist/index-s9yOFU37.d.cts +604 -0
  123. package/dist/index-s9yOFU37.d.ts +604 -0
  124. package/dist/index.cjs +22966 -6960
  125. package/dist/index.d.cts +864 -20
  126. package/dist/index.d.ts +864 -20
  127. package/dist/index.js +3062 -48
  128. package/dist/input/index.cjs +1 -1
  129. package/dist/input/index.js +1 -1
  130. package/dist/orbital/index.cjs +3 -3
  131. package/dist/orbital/index.d.cts +1 -1
  132. package/dist/orbital/index.d.ts +1 -1
  133. package/dist/orbital/index.js +1 -1
  134. package/dist/particles/index.cjs +16 -16
  135. package/dist/particles/index.d.cts +1 -1
  136. package/dist/particles/index.d.ts +1 -1
  137. package/dist/particles/index.js +1 -1
  138. package/dist/physics/index.cjs +2377 -21
  139. package/dist/physics/index.d.cts +1 -1
  140. package/dist/physics/index.d.ts +1 -1
  141. package/dist/physics/index.js +35 -1
  142. package/dist/postfx/index.cjs +3491 -0
  143. package/dist/postfx/index.js +93 -0
  144. package/dist/procedural/index.cjs +1 -1
  145. package/dist/procedural/index.js +1 -1
  146. package/dist/puppeteer-5VF6KDVO.js +52197 -0
  147. package/dist/puppeteer-IZVZ3SG4.js +52197 -0
  148. package/dist/rendering/index.cjs +33 -32
  149. package/dist/rendering/index.d.cts +1 -1
  150. package/dist/rendering/index.d.ts +1 -1
  151. package/dist/rendering/index.js +8 -6
  152. package/dist/runtime/index.cjs +23 -13
  153. package/dist/runtime/index.d.cts +1 -1
  154. package/dist/runtime/index.d.ts +1 -1
  155. package/dist/runtime/index.js +8 -6
  156. package/dist/runtime/protocols/index.cjs +349 -0
  157. package/dist/runtime/protocols/index.js +15 -0
  158. package/dist/scene/index.cjs +8 -8
  159. package/dist/scene/index.d.cts +1 -1
  160. package/dist/scene/index.d.ts +1 -1
  161. package/dist/scene/index.js +1 -1
  162. package/dist/shader/index.cjs +3087 -0
  163. package/dist/shader/index.js +3044 -0
  164. package/dist/simulation/index.cjs +10680 -0
  165. package/dist/simulation/index.d.cts +3 -0
  166. package/dist/simulation/index.d.ts +3 -0
  167. package/dist/simulation/index.js +307 -0
  168. package/dist/spatial/index.cjs +2443 -0
  169. package/dist/spatial/index.d.cts +1545 -0
  170. package/dist/spatial/index.d.ts +1545 -0
  171. package/dist/spatial/index.js +2400 -0
  172. package/dist/terrain/index.cjs +1 -1
  173. package/dist/terrain/index.d.cts +1 -1
  174. package/dist/terrain/index.d.ts +1 -1
  175. package/dist/terrain/index.js +1 -1
  176. package/dist/transformers.node-4NKAPD5U.js +45620 -0
  177. package/dist/vm/index.cjs +7 -8
  178. package/dist/vm/index.d.cts +1 -1
  179. package/dist/vm/index.d.ts +1 -1
  180. package/dist/vm/index.js +1 -1
  181. package/dist/vm-bridge/index.cjs +2 -2
  182. package/dist/vm-bridge/index.d.cts +2 -2
  183. package/dist/vm-bridge/index.d.ts +2 -2
  184. package/dist/vm-bridge/index.js +1 -1
  185. package/dist/vr/index.cjs +6 -6
  186. package/dist/vr/index.js +1 -1
  187. package/dist/world/index.cjs +3 -3
  188. package/dist/world/index.d.cts +1 -1
  189. package/dist/world/index.d.ts +1 -1
  190. package/dist/world/index.js +1 -1
  191. package/package.json +53 -21
  192. package/LICENSE +0 -21
@@ -0,0 +1,3044 @@
1
+ import "../chunk-AKLW2MUS.js";
2
+
3
+ // src/shader/graph/ShaderGraphTypes.ts
4
+ var TYPE_SIZES = {
5
+ float: 1,
6
+ vec2: 2,
7
+ vec3: 3,
8
+ vec4: 4,
9
+ mat2: 4,
10
+ mat3: 9,
11
+ mat4: 16,
12
+ int: 1,
13
+ ivec2: 2,
14
+ ivec3: 3,
15
+ ivec4: 4,
16
+ bool: 1,
17
+ sampler2D: 0,
18
+ samplerCube: 0
19
+ };
20
+ function areTypesCompatible(from, to) {
21
+ if (from === to) return true;
22
+ if (from === "sampler2D" || from === "samplerCube") return false;
23
+ if (to === "sampler2D" || to === "samplerCube") return false;
24
+ if (from === "float" && (to === "vec2" || to === "vec3" || to === "vec4")) return true;
25
+ if (from === "int" && to === "float") return true;
26
+ if (from === "vec3" && to === "vec4") return true;
27
+ if (from === "vec4" && to === "vec3") return true;
28
+ return false;
29
+ }
30
+ function getTypeConversion(from, to, expr) {
31
+ if (from === to) return expr;
32
+ if (from === "float") {
33
+ if (to === "vec2") return `vec2<f32>(${expr})`;
34
+ if (to === "vec3") return `vec3<f32>(${expr})`;
35
+ if (to === "vec4") return `vec4<f32>(${expr})`;
36
+ }
37
+ if (from === "int" && to === "float") return `f32(${expr})`;
38
+ if (from === "vec3" && to === "vec4") return `vec4<f32>(${expr}, 1.0)`;
39
+ if (from === "vec4" && to === "vec3") return `(${expr}).xyz`;
40
+ return expr;
41
+ }
42
+ var INPUT_NODES = [
43
+ {
44
+ type: "input_position",
45
+ name: "World Position",
46
+ category: "input",
47
+ description: "World space position",
48
+ inputs: [],
49
+ outputs: [{ id: "position", name: "Position", type: "vec3" }],
50
+ generateCode: () => "in.worldPosition"
51
+ },
52
+ {
53
+ type: "input_normal",
54
+ name: "World Normal",
55
+ category: "input",
56
+ description: "World space normal",
57
+ inputs: [],
58
+ outputs: [{ id: "normal", name: "Normal", type: "vec3" }],
59
+ generateCode: () => "in.worldNormal"
60
+ },
61
+ {
62
+ type: "input_uv",
63
+ name: "UV Coordinates",
64
+ category: "input",
65
+ description: "Texture coordinates",
66
+ inputs: [],
67
+ outputs: [{ id: "uv", name: "UV", type: "vec2" }],
68
+ generateCode: () => "in.uv"
69
+ },
70
+ {
71
+ type: "input_time",
72
+ name: "Time",
73
+ category: "input",
74
+ description: "Scene time in seconds",
75
+ inputs: [],
76
+ outputs: [{ id: "time", name: "Time", type: "float" }],
77
+ generateCode: () => "scene.time"
78
+ },
79
+ {
80
+ type: "input_camera_position",
81
+ name: "Camera Position",
82
+ category: "input",
83
+ description: "Camera world position",
84
+ inputs: [],
85
+ outputs: [{ id: "position", name: "Position", type: "vec3" }],
86
+ generateCode: () => "camera.position"
87
+ },
88
+ {
89
+ type: "input_view_direction",
90
+ name: "View Direction",
91
+ category: "input",
92
+ description: "Direction from surface to camera",
93
+ inputs: [],
94
+ outputs: [{ id: "direction", name: "Direction", type: "vec3" }],
95
+ generateCode: () => "normalize(camera.position - in.worldPosition)"
96
+ },
97
+ {
98
+ type: "constant_float",
99
+ name: "Float",
100
+ category: "input",
101
+ description: "Constant float value",
102
+ inputs: [],
103
+ outputs: [{ id: "value", name: "Value", type: "float" }],
104
+ defaultProperties: { value: 0 },
105
+ generateCode: (node) => `${node.properties?.value ?? 0}`
106
+ },
107
+ {
108
+ type: "constant_vec2",
109
+ name: "Vector2",
110
+ category: "input",
111
+ description: "Constant vec2 value",
112
+ inputs: [],
113
+ outputs: [{ id: "value", name: "Value", type: "vec2" }],
114
+ defaultProperties: { x: 0, y: 0 },
115
+ generateCode: (node) => {
116
+ const x = node.properties?.x ?? 0;
117
+ const y = node.properties?.y ?? 0;
118
+ return `vec2<f32>(${x}, ${y})`;
119
+ }
120
+ },
121
+ {
122
+ type: "constant_vec3",
123
+ name: "Vector3",
124
+ category: "input",
125
+ description: "Constant vec3 value",
126
+ inputs: [],
127
+ outputs: [{ id: "value", name: "Value", type: "vec3" }],
128
+ defaultProperties: { x: 0, y: 0, z: 0 },
129
+ generateCode: (node) => {
130
+ const x = node.properties?.x ?? 0;
131
+ const y = node.properties?.y ?? 0;
132
+ const z = node.properties?.z ?? 0;
133
+ return `vec3<f32>(${x}, ${y}, ${z})`;
134
+ }
135
+ },
136
+ {
137
+ type: "constant_color",
138
+ name: "Color",
139
+ category: "input",
140
+ description: "Constant color value",
141
+ inputs: [],
142
+ outputs: [{ id: "color", name: "Color", type: "vec4" }],
143
+ defaultProperties: { r: 1, g: 1, b: 1, a: 1 },
144
+ generateCode: (node) => {
145
+ const r = node.properties?.r ?? 1;
146
+ const g = node.properties?.g ?? 1;
147
+ const b = node.properties?.b ?? 1;
148
+ const a = node.properties?.a ?? 1;
149
+ return `vec4<f32>(${r}, ${g}, ${b}, ${a})`;
150
+ }
151
+ }
152
+ ];
153
+ var MATH_NODES = [
154
+ {
155
+ type: "math_add",
156
+ name: "Add",
157
+ category: "math",
158
+ description: "Add two values",
159
+ inputs: [
160
+ { id: "a", name: "A", type: "float", defaultValue: 0 },
161
+ { id: "b", name: "B", type: "float", defaultValue: 0 }
162
+ ],
163
+ outputs: [{ id: "result", name: "Result", type: "float" }],
164
+ generateCode: (_, inputs) => `(${inputs.a} + ${inputs.b})`
165
+ },
166
+ {
167
+ type: "math_subtract",
168
+ name: "Subtract",
169
+ category: "math",
170
+ description: "Subtract two values",
171
+ inputs: [
172
+ { id: "a", name: "A", type: "float", defaultValue: 0 },
173
+ { id: "b", name: "B", type: "float", defaultValue: 0 }
174
+ ],
175
+ outputs: [{ id: "result", name: "Result", type: "float" }],
176
+ generateCode: (_, inputs) => `(${inputs.a} - ${inputs.b})`
177
+ },
178
+ {
179
+ type: "math_multiply",
180
+ name: "Multiply",
181
+ category: "math",
182
+ description: "Multiply two values",
183
+ inputs: [
184
+ { id: "a", name: "A", type: "float", defaultValue: 1 },
185
+ { id: "b", name: "B", type: "float", defaultValue: 1 }
186
+ ],
187
+ outputs: [{ id: "result", name: "Result", type: "float" }],
188
+ generateCode: (_, inputs) => `(${inputs.a} * ${inputs.b})`
189
+ },
190
+ {
191
+ type: "math_divide",
192
+ name: "Divide",
193
+ category: "math",
194
+ description: "Divide two values",
195
+ inputs: [
196
+ { id: "a", name: "A", type: "float", defaultValue: 1 },
197
+ { id: "b", name: "B", type: "float", defaultValue: 1 }
198
+ ],
199
+ outputs: [{ id: "result", name: "Result", type: "float" }],
200
+ generateCode: (_, inputs) => `(${inputs.a} / max(${inputs.b}, 0.0001))`
201
+ },
202
+ {
203
+ type: "math_power",
204
+ name: "Power",
205
+ category: "math",
206
+ description: "Raise to power",
207
+ inputs: [
208
+ { id: "base", name: "Base", type: "float", defaultValue: 2 },
209
+ { id: "exp", name: "Exponent", type: "float", defaultValue: 2 }
210
+ ],
211
+ outputs: [{ id: "result", name: "Result", type: "float" }],
212
+ generateCode: (_, inputs) => `pow(${inputs.base}, ${inputs.exp})`
213
+ },
214
+ {
215
+ type: "math_sqrt",
216
+ name: "Square Root",
217
+ category: "math",
218
+ description: "Square root",
219
+ inputs: [{ id: "value", name: "Value", type: "float", defaultValue: 1 }],
220
+ outputs: [{ id: "result", name: "Result", type: "float" }],
221
+ generateCode: (_, inputs) => `sqrt(max(${inputs.value}, 0.0))`
222
+ },
223
+ {
224
+ type: "math_abs",
225
+ name: "Absolute",
226
+ category: "math",
227
+ description: "Absolute value",
228
+ inputs: [{ id: "value", name: "Value", type: "float", defaultValue: 0 }],
229
+ outputs: [{ id: "result", name: "Result", type: "float" }],
230
+ generateCode: (_, inputs) => `abs(${inputs.value})`
231
+ },
232
+ {
233
+ type: "math_negate",
234
+ name: "Negate",
235
+ category: "math",
236
+ description: "Negate value",
237
+ inputs: [{ id: "value", name: "Value", type: "float", defaultValue: 0 }],
238
+ outputs: [{ id: "result", name: "Result", type: "float" }],
239
+ generateCode: (_, inputs) => `(-${inputs.value})`
240
+ },
241
+ {
242
+ type: "math_min",
243
+ name: "Minimum",
244
+ category: "math",
245
+ description: "Minimum of two values",
246
+ inputs: [
247
+ { id: "a", name: "A", type: "float", defaultValue: 0 },
248
+ { id: "b", name: "B", type: "float", defaultValue: 0 }
249
+ ],
250
+ outputs: [{ id: "result", name: "Result", type: "float" }],
251
+ generateCode: (_, inputs) => `min(${inputs.a}, ${inputs.b})`
252
+ },
253
+ {
254
+ type: "math_max",
255
+ name: "Maximum",
256
+ category: "math",
257
+ description: "Maximum of two values",
258
+ inputs: [
259
+ { id: "a", name: "A", type: "float", defaultValue: 0 },
260
+ { id: "b", name: "B", type: "float", defaultValue: 0 }
261
+ ],
262
+ outputs: [{ id: "result", name: "Result", type: "float" }],
263
+ generateCode: (_, inputs) => `max(${inputs.a}, ${inputs.b})`
264
+ },
265
+ {
266
+ type: "math_clamp",
267
+ name: "Clamp",
268
+ category: "math",
269
+ description: "Clamp value between min and max",
270
+ inputs: [
271
+ { id: "value", name: "Value", type: "float", defaultValue: 0 },
272
+ { id: "min", name: "Min", type: "float", defaultValue: 0 },
273
+ { id: "max", name: "Max", type: "float", defaultValue: 1 }
274
+ ],
275
+ outputs: [{ id: "result", name: "Result", type: "float" }],
276
+ generateCode: (_, inputs) => `clamp(${inputs.value}, ${inputs.min}, ${inputs.max})`
277
+ },
278
+ {
279
+ type: "math_saturate",
280
+ name: "Saturate",
281
+ category: "math",
282
+ description: "Clamp between 0 and 1",
283
+ inputs: [{ id: "value", name: "Value", type: "float", defaultValue: 0 }],
284
+ outputs: [{ id: "result", name: "Result", type: "float" }],
285
+ generateCode: (_, inputs) => `saturate(${inputs.value})`
286
+ },
287
+ {
288
+ type: "math_lerp",
289
+ name: "Lerp",
290
+ category: "math",
291
+ description: "Linear interpolation",
292
+ inputs: [
293
+ { id: "a", name: "A", type: "float", defaultValue: 0 },
294
+ { id: "b", name: "B", type: "float", defaultValue: 1 },
295
+ { id: "t", name: "T", type: "float", defaultValue: 0.5 }
296
+ ],
297
+ outputs: [{ id: "result", name: "Result", type: "float" }],
298
+ generateCode: (_, inputs) => `mix(${inputs.a}, ${inputs.b}, ${inputs.t})`
299
+ },
300
+ {
301
+ type: "math_smoothstep",
302
+ name: "Smoothstep",
303
+ category: "math",
304
+ description: "Smooth Hermite interpolation",
305
+ inputs: [
306
+ { id: "edge0", name: "Edge0", type: "float", defaultValue: 0 },
307
+ { id: "edge1", name: "Edge1", type: "float", defaultValue: 1 },
308
+ { id: "x", name: "X", type: "float", defaultValue: 0.5 }
309
+ ],
310
+ outputs: [{ id: "result", name: "Result", type: "float" }],
311
+ generateCode: (_, inputs) => `smoothstep(${inputs.edge0}, ${inputs.edge1}, ${inputs.x})`
312
+ },
313
+ {
314
+ type: "math_step",
315
+ name: "Step",
316
+ category: "math",
317
+ description: "Step function",
318
+ inputs: [
319
+ { id: "edge", name: "Edge", type: "float", defaultValue: 0.5 },
320
+ { id: "x", name: "X", type: "float", defaultValue: 0 }
321
+ ],
322
+ outputs: [{ id: "result", name: "Result", type: "float" }],
323
+ generateCode: (_, inputs) => `step(${inputs.edge}, ${inputs.x})`
324
+ },
325
+ {
326
+ type: "math_fract",
327
+ name: "Fraction",
328
+ category: "math",
329
+ description: "Fractional part",
330
+ inputs: [{ id: "value", name: "Value", type: "float", defaultValue: 0 }],
331
+ outputs: [{ id: "result", name: "Result", type: "float" }],
332
+ generateCode: (_, inputs) => `fract(${inputs.value})`
333
+ },
334
+ {
335
+ type: "math_floor",
336
+ name: "Floor",
337
+ category: "math",
338
+ description: "Floor value",
339
+ inputs: [{ id: "value", name: "Value", type: "float", defaultValue: 0 }],
340
+ outputs: [{ id: "result", name: "Result", type: "float" }],
341
+ generateCode: (_, inputs) => `floor(${inputs.value})`
342
+ },
343
+ {
344
+ type: "math_ceil",
345
+ name: "Ceiling",
346
+ category: "math",
347
+ description: "Ceiling value",
348
+ inputs: [{ id: "value", name: "Value", type: "float", defaultValue: 0 }],
349
+ outputs: [{ id: "result", name: "Result", type: "float" }],
350
+ generateCode: (_, inputs) => `ceil(${inputs.value})`
351
+ },
352
+ {
353
+ type: "math_round",
354
+ name: "Round",
355
+ category: "math",
356
+ description: "Round value",
357
+ inputs: [{ id: "value", name: "Value", type: "float", defaultValue: 0 }],
358
+ outputs: [{ id: "result", name: "Result", type: "float" }],
359
+ generateCode: (_, inputs) => `round(${inputs.value})`
360
+ },
361
+ {
362
+ type: "math_mod",
363
+ name: "Modulo",
364
+ category: "math",
365
+ description: "Modulo operation",
366
+ inputs: [
367
+ { id: "a", name: "A", type: "float", defaultValue: 0 },
368
+ { id: "b", name: "B", type: "float", defaultValue: 1 }
369
+ ],
370
+ outputs: [{ id: "result", name: "Result", type: "float" }],
371
+ generateCode: (_, inputs) => `(${inputs.a} % ${inputs.b})`
372
+ }
373
+ ];
374
+ var TRIG_NODES = [
375
+ {
376
+ type: "trig_sin",
377
+ name: "Sine",
378
+ category: "math",
379
+ description: "Sine function",
380
+ inputs: [{ id: "angle", name: "Angle", type: "float", defaultValue: 0 }],
381
+ outputs: [{ id: "result", name: "Result", type: "float" }],
382
+ generateCode: (_, inputs) => `sin(${inputs.angle})`
383
+ },
384
+ {
385
+ type: "trig_cos",
386
+ name: "Cosine",
387
+ category: "math",
388
+ description: "Cosine function",
389
+ inputs: [{ id: "angle", name: "Angle", type: "float", defaultValue: 0 }],
390
+ outputs: [{ id: "result", name: "Result", type: "float" }],
391
+ generateCode: (_, inputs) => `cos(${inputs.angle})`
392
+ },
393
+ {
394
+ type: "trig_tan",
395
+ name: "Tangent",
396
+ category: "math",
397
+ description: "Tangent function",
398
+ inputs: [{ id: "angle", name: "Angle", type: "float", defaultValue: 0 }],
399
+ outputs: [{ id: "result", name: "Result", type: "float" }],
400
+ generateCode: (_, inputs) => `tan(${inputs.angle})`
401
+ },
402
+ {
403
+ type: "trig_asin",
404
+ name: "Arcsine",
405
+ category: "math",
406
+ description: "Arcsine function",
407
+ inputs: [{ id: "value", name: "Value", type: "float", defaultValue: 0 }],
408
+ outputs: [{ id: "result", name: "Result", type: "float" }],
409
+ generateCode: (_, inputs) => `asin(${inputs.value})`
410
+ },
411
+ {
412
+ type: "trig_acos",
413
+ name: "Arccosine",
414
+ category: "math",
415
+ description: "Arccosine function",
416
+ inputs: [{ id: "value", name: "Value", type: "float", defaultValue: 0 }],
417
+ outputs: [{ id: "result", name: "Result", type: "float" }],
418
+ generateCode: (_, inputs) => `acos(${inputs.value})`
419
+ },
420
+ {
421
+ type: "trig_atan",
422
+ name: "Arctangent",
423
+ category: "math",
424
+ description: "Arctangent function",
425
+ inputs: [{ id: "value", name: "Value", type: "float", defaultValue: 0 }],
426
+ outputs: [{ id: "result", name: "Result", type: "float" }],
427
+ generateCode: (_, inputs) => `atan(${inputs.value})`
428
+ },
429
+ {
430
+ type: "trig_atan2",
431
+ name: "Arctangent2",
432
+ category: "math",
433
+ description: "Two-argument arctangent",
434
+ inputs: [
435
+ { id: "y", name: "Y", type: "float", defaultValue: 0 },
436
+ { id: "x", name: "X", type: "float", defaultValue: 1 }
437
+ ],
438
+ outputs: [{ id: "result", name: "Result", type: "float" }],
439
+ generateCode: (_, inputs) => `atan2(${inputs.y}, ${inputs.x})`
440
+ },
441
+ {
442
+ type: "trig_radians",
443
+ name: "Degrees to Radians",
444
+ category: "math",
445
+ description: "Convert degrees to radians",
446
+ inputs: [{ id: "degrees", name: "Degrees", type: "float", defaultValue: 0 }],
447
+ outputs: [{ id: "result", name: "Radians", type: "float" }],
448
+ generateCode: (_, inputs) => `radians(${inputs.degrees})`
449
+ },
450
+ {
451
+ type: "trig_degrees",
452
+ name: "Radians to Degrees",
453
+ category: "math",
454
+ description: "Convert radians to degrees",
455
+ inputs: [{ id: "radians", name: "Radians", type: "float", defaultValue: 0 }],
456
+ outputs: [{ id: "result", name: "Degrees", type: "float" }],
457
+ generateCode: (_, inputs) => `degrees(${inputs.radians})`
458
+ }
459
+ ];
460
+ var VECTOR_NODES = [
461
+ {
462
+ type: "vector_make_vec2",
463
+ name: "Make Vec2",
464
+ category: "vector",
465
+ description: "Create vec2 from components",
466
+ inputs: [
467
+ { id: "x", name: "X", type: "float", defaultValue: 0 },
468
+ { id: "y", name: "Y", type: "float", defaultValue: 0 }
469
+ ],
470
+ outputs: [{ id: "vector", name: "Vector", type: "vec2" }],
471
+ generateCode: (_, inputs) => `vec2<f32>(${inputs.x}, ${inputs.y})`
472
+ },
473
+ {
474
+ type: "vector_make_vec3",
475
+ name: "Make Vec3",
476
+ category: "vector",
477
+ description: "Create vec3 from components",
478
+ inputs: [
479
+ { id: "x", name: "X", type: "float", defaultValue: 0 },
480
+ { id: "y", name: "Y", type: "float", defaultValue: 0 },
481
+ { id: "z", name: "Z", type: "float", defaultValue: 0 }
482
+ ],
483
+ outputs: [{ id: "vector", name: "Vector", type: "vec3" }],
484
+ generateCode: (_, inputs) => `vec3<f32>(${inputs.x}, ${inputs.y}, ${inputs.z})`
485
+ },
486
+ {
487
+ type: "vector_make_vec4",
488
+ name: "Make Vec4",
489
+ category: "vector",
490
+ description: "Create vec4 from components",
491
+ inputs: [
492
+ { id: "x", name: "X", type: "float", defaultValue: 0 },
493
+ { id: "y", name: "Y", type: "float", defaultValue: 0 },
494
+ { id: "z", name: "Z", type: "float", defaultValue: 0 },
495
+ { id: "w", name: "W", type: "float", defaultValue: 1 }
496
+ ],
497
+ outputs: [{ id: "vector", name: "Vector", type: "vec4" }],
498
+ generateCode: (_, inputs) => `vec4<f32>(${inputs.x}, ${inputs.y}, ${inputs.z}, ${inputs.w})`
499
+ },
500
+ {
501
+ type: "vector_split_vec2",
502
+ name: "Split Vec2",
503
+ category: "vector",
504
+ description: "Split vec2 into components",
505
+ inputs: [{ id: "vector", name: "Vector", type: "vec2", defaultValue: [0, 0] }],
506
+ outputs: [
507
+ { id: "x", name: "X", type: "float" },
508
+ { id: "y", name: "Y", type: "float" }
509
+ ],
510
+ generateCode: (_, inputs) => inputs.vector
511
+ },
512
+ {
513
+ type: "vector_split_vec3",
514
+ name: "Split Vec3",
515
+ category: "vector",
516
+ description: "Split vec3 into components",
517
+ inputs: [{ id: "vector", name: "Vector", type: "vec3", defaultValue: [0, 0, 0] }],
518
+ outputs: [
519
+ { id: "x", name: "X", type: "float" },
520
+ { id: "y", name: "Y", type: "float" },
521
+ { id: "z", name: "Z", type: "float" }
522
+ ],
523
+ generateCode: (_, inputs) => inputs.vector
524
+ },
525
+ {
526
+ type: "vector_split_vec4",
527
+ name: "Split Vec4",
528
+ category: "vector",
529
+ description: "Split vec4 into components",
530
+ inputs: [{ id: "vector", name: "Vector", type: "vec4", defaultValue: [0, 0, 0, 1] }],
531
+ outputs: [
532
+ { id: "x", name: "X", type: "float" },
533
+ { id: "y", name: "Y", type: "float" },
534
+ { id: "z", name: "Z", type: "float" },
535
+ { id: "w", name: "W", type: "float" }
536
+ ],
537
+ generateCode: (_, inputs) => inputs.vector
538
+ },
539
+ {
540
+ type: "vector_normalize",
541
+ name: "Normalize",
542
+ category: "vector",
543
+ description: "Normalize vector",
544
+ inputs: [{ id: "vector", name: "Vector", type: "vec3", defaultValue: [1, 0, 0] }],
545
+ outputs: [{ id: "result", name: "Result", type: "vec3" }],
546
+ generateCode: (_, inputs) => `normalize(${inputs.vector})`
547
+ },
548
+ {
549
+ type: "vector_length",
550
+ name: "Length",
551
+ category: "vector",
552
+ description: "Vector length",
553
+ inputs: [{ id: "vector", name: "Vector", type: "vec3", defaultValue: [0, 0, 0] }],
554
+ outputs: [{ id: "length", name: "Length", type: "float" }],
555
+ generateCode: (_, inputs) => `length(${inputs.vector})`
556
+ },
557
+ {
558
+ type: "vector_distance",
559
+ name: "Distance",
560
+ category: "vector",
561
+ description: "Distance between vectors",
562
+ inputs: [
563
+ { id: "a", name: "A", type: "vec3", defaultValue: [0, 0, 0] },
564
+ { id: "b", name: "B", type: "vec3", defaultValue: [0, 0, 0] }
565
+ ],
566
+ outputs: [{ id: "distance", name: "Distance", type: "float" }],
567
+ generateCode: (_, inputs) => `distance(${inputs.a}, ${inputs.b})`
568
+ },
569
+ {
570
+ type: "vector_dot",
571
+ name: "Dot Product",
572
+ category: "vector",
573
+ description: "Dot product",
574
+ inputs: [
575
+ { id: "a", name: "A", type: "vec3", defaultValue: [1, 0, 0] },
576
+ { id: "b", name: "B", type: "vec3", defaultValue: [1, 0, 0] }
577
+ ],
578
+ outputs: [{ id: "result", name: "Result", type: "float" }],
579
+ generateCode: (_, inputs) => `dot(${inputs.a}, ${inputs.b})`
580
+ },
581
+ {
582
+ type: "vector_cross",
583
+ name: "Cross Product",
584
+ category: "vector",
585
+ description: "Cross product",
586
+ inputs: [
587
+ { id: "a", name: "A", type: "vec3", defaultValue: [1, 0, 0] },
588
+ { id: "b", name: "B", type: "vec3", defaultValue: [0, 1, 0] }
589
+ ],
590
+ outputs: [{ id: "result", name: "Result", type: "vec3" }],
591
+ generateCode: (_, inputs) => `cross(${inputs.a}, ${inputs.b})`
592
+ },
593
+ {
594
+ type: "vector_reflect",
595
+ name: "Reflect",
596
+ category: "vector",
597
+ description: "Reflect vector",
598
+ inputs: [
599
+ { id: "incident", name: "Incident", type: "vec3", defaultValue: [1, 0, 0] },
600
+ { id: "normal", name: "Normal", type: "vec3", defaultValue: [0, 1, 0] }
601
+ ],
602
+ outputs: [{ id: "result", name: "Result", type: "vec3" }],
603
+ generateCode: (_, inputs) => `reflect(${inputs.incident}, ${inputs.normal})`
604
+ },
605
+ {
606
+ type: "vector_refract",
607
+ name: "Refract",
608
+ category: "vector",
609
+ description: "Refract vector",
610
+ inputs: [
611
+ { id: "incident", name: "Incident", type: "vec3", defaultValue: [1, 0, 0] },
612
+ { id: "normal", name: "Normal", type: "vec3", defaultValue: [0, 1, 0] },
613
+ { id: "eta", name: "Eta", type: "float", defaultValue: 1 }
614
+ ],
615
+ outputs: [{ id: "result", name: "Result", type: "vec3" }],
616
+ generateCode: (_, inputs) => `refract(${inputs.incident}, ${inputs.normal}, ${inputs.eta})`
617
+ }
618
+ ];
619
+ var COLOR_NODES = [
620
+ {
621
+ type: "color_blend",
622
+ name: "Blend",
623
+ category: "color",
624
+ description: "Blend two colors",
625
+ inputs: [
626
+ { id: "a", name: "A", type: "vec4", defaultValue: [0, 0, 0, 1] },
627
+ { id: "b", name: "B", type: "vec4", defaultValue: [1, 1, 1, 1] },
628
+ { id: "factor", name: "Factor", type: "float", defaultValue: 0.5 }
629
+ ],
630
+ outputs: [{ id: "result", name: "Result", type: "vec4" }],
631
+ generateCode: (_, inputs) => `mix(${inputs.a}, ${inputs.b}, ${inputs.factor})`
632
+ },
633
+ {
634
+ type: "color_hue_shift",
635
+ name: "Hue Shift",
636
+ category: "color",
637
+ description: "Shift hue of color",
638
+ inputs: [
639
+ { id: "color", name: "Color", type: "vec3", defaultValue: [1, 0, 0] },
640
+ { id: "shift", name: "Shift", type: "float", defaultValue: 0 }
641
+ ],
642
+ outputs: [{ id: "result", name: "Result", type: "vec3" }],
643
+ generateCode: (_, inputs) => `hueShift(${inputs.color}, ${inputs.shift})`
644
+ },
645
+ {
646
+ type: "color_saturation",
647
+ name: "Saturation",
648
+ category: "color",
649
+ description: "Adjust color saturation",
650
+ inputs: [
651
+ { id: "color", name: "Color", type: "vec3", defaultValue: [1, 0.5, 0.5] },
652
+ { id: "saturation", name: "Saturation", type: "float", defaultValue: 1 }
653
+ ],
654
+ outputs: [{ id: "result", name: "Result", type: "vec3" }],
655
+ generateCode: (_, inputs) => {
656
+ return `mix(vec3<f32>(dot(${inputs.color}, vec3<f32>(0.299, 0.587, 0.114))), ${inputs.color}, ${inputs.saturation})`;
657
+ }
658
+ },
659
+ {
660
+ type: "color_brightness",
661
+ name: "Brightness",
662
+ category: "color",
663
+ description: "Adjust color brightness",
664
+ inputs: [
665
+ { id: "color", name: "Color", type: "vec3", defaultValue: [0.5, 0.5, 0.5] },
666
+ { id: "brightness", name: "Brightness", type: "float", defaultValue: 1 }
667
+ ],
668
+ outputs: [{ id: "result", name: "Result", type: "vec3" }],
669
+ generateCode: (_, inputs) => `(${inputs.color} * ${inputs.brightness})`
670
+ },
671
+ {
672
+ type: "color_contrast",
673
+ name: "Contrast",
674
+ category: "color",
675
+ description: "Adjust color contrast",
676
+ inputs: [
677
+ { id: "color", name: "Color", type: "vec3", defaultValue: [0.5, 0.5, 0.5] },
678
+ { id: "contrast", name: "Contrast", type: "float", defaultValue: 1 }
679
+ ],
680
+ outputs: [{ id: "result", name: "Result", type: "vec3" }],
681
+ generateCode: (_, inputs) => `((${inputs.color} - vec3<f32>(0.5)) * ${inputs.contrast} + vec3<f32>(0.5))`
682
+ },
683
+ {
684
+ type: "color_invert",
685
+ name: "Invert",
686
+ category: "color",
687
+ description: "Invert color",
688
+ inputs: [{ id: "color", name: "Color", type: "vec3", defaultValue: [0.5, 0.5, 0.5] }],
689
+ outputs: [{ id: "result", name: "Result", type: "vec3" }],
690
+ generateCode: (_, inputs) => `(vec3<f32>(1.0) - ${inputs.color})`
691
+ },
692
+ {
693
+ type: "color_grayscale",
694
+ name: "Grayscale",
695
+ category: "color",
696
+ description: "Convert to grayscale",
697
+ inputs: [{ id: "color", name: "Color", type: "vec3", defaultValue: [1, 0, 0] }],
698
+ outputs: [{ id: "result", name: "Gray", type: "float" }],
699
+ generateCode: (_, inputs) => `dot(${inputs.color}, vec3<f32>(0.299, 0.587, 0.114))`
700
+ }
701
+ ];
702
+ var TEXTURE_NODES = [
703
+ {
704
+ type: "texture_sample",
705
+ name: "Sample Texture",
706
+ category: "texture",
707
+ description: "Sample a 2D texture",
708
+ inputs: [
709
+ { id: "texture", name: "Texture", type: "sampler2D" },
710
+ { id: "uv", name: "UV", type: "vec2", defaultValue: [0, 0] }
711
+ ],
712
+ outputs: [{ id: "color", name: "Color", type: "vec4" }],
713
+ generateCode: (node, inputs) => {
714
+ const textureId = node.properties?.textureId ?? "defaultTexture";
715
+ return `textureSample(${textureId}, ${textureId}Sampler, ${inputs.uv})`;
716
+ }
717
+ },
718
+ {
719
+ type: "texture_sample_level",
720
+ name: "Sample Texture Level",
721
+ category: "texture",
722
+ description: "Sample texture at specific mip level",
723
+ inputs: [
724
+ { id: "texture", name: "Texture", type: "sampler2D" },
725
+ { id: "uv", name: "UV", type: "vec2", defaultValue: [0, 0] },
726
+ { id: "level", name: "Level", type: "float", defaultValue: 0 }
727
+ ],
728
+ outputs: [{ id: "color", name: "Color", type: "vec4" }],
729
+ generateCode: (node, inputs) => {
730
+ const textureId = node.properties?.textureId ?? "defaultTexture";
731
+ return `textureSampleLevel(${textureId}, ${textureId}Sampler, ${inputs.uv}, ${inputs.level})`;
732
+ }
733
+ },
734
+ {
735
+ type: "texture_tiling_offset",
736
+ name: "Tiling and Offset",
737
+ category: "texture",
738
+ description: "Apply tiling and offset to UVs",
739
+ inputs: [
740
+ { id: "uv", name: "UV", type: "vec2", defaultValue: [0, 0] },
741
+ { id: "tiling", name: "Tiling", type: "vec2", defaultValue: [1, 1] },
742
+ { id: "offset", name: "Offset", type: "vec2", defaultValue: [0, 0] }
743
+ ],
744
+ outputs: [{ id: "result", name: "Result", type: "vec2" }],
745
+ generateCode: (_, inputs) => `(${inputs.uv} * ${inputs.tiling} + ${inputs.offset})`
746
+ }
747
+ ];
748
+ var UTILITY_NODES = [
749
+ {
750
+ type: "utility_fresnel",
751
+ name: "Fresnel",
752
+ category: "utility",
753
+ description: "Fresnel effect",
754
+ inputs: [
755
+ { id: "normal", name: "Normal", type: "vec3", defaultValue: [0, 1, 0] },
756
+ { id: "viewDir", name: "View Dir", type: "vec3", defaultValue: [0, 0, 1] },
757
+ { id: "power", name: "Power", type: "float", defaultValue: 5 }
758
+ ],
759
+ outputs: [{ id: "result", name: "Result", type: "float" }],
760
+ generateCode: (_, inputs) => `pow(1.0 - saturate(dot(${inputs.normal}, ${inputs.viewDir})), ${inputs.power})`
761
+ },
762
+ {
763
+ type: "utility_noise_simple",
764
+ name: "Simple Noise",
765
+ category: "utility",
766
+ description: "Simple value noise",
767
+ inputs: [{ id: "uv", name: "UV", type: "vec2", defaultValue: [0, 0] }],
768
+ outputs: [{ id: "noise", name: "Noise", type: "float" }],
769
+ generateCode: (_, inputs) => `simpleNoise(${inputs.uv})`
770
+ },
771
+ {
772
+ type: "utility_gradient_noise",
773
+ name: "Gradient Noise",
774
+ category: "utility",
775
+ description: "Perlin-like gradient noise",
776
+ inputs: [{ id: "uv", name: "UV", type: "vec2", defaultValue: [0, 0] }],
777
+ outputs: [{ id: "noise", name: "Noise", type: "float" }],
778
+ generateCode: (_, inputs) => `gradientNoise(${inputs.uv})`
779
+ },
780
+ {
781
+ type: "utility_voronoi",
782
+ name: "Voronoi",
783
+ category: "utility",
784
+ description: "Voronoi noise",
785
+ inputs: [
786
+ { id: "uv", name: "UV", type: "vec2", defaultValue: [0, 0] },
787
+ { id: "scale", name: "Scale", type: "float", defaultValue: 5 }
788
+ ],
789
+ outputs: [
790
+ { id: "cells", name: "Cells", type: "float" },
791
+ { id: "distance", name: "Distance", type: "float" }
792
+ ],
793
+ generateCode: (_, inputs) => `voronoi(${inputs.uv} * ${inputs.scale})`
794
+ },
795
+ {
796
+ type: "utility_remap",
797
+ name: "Remap",
798
+ category: "utility",
799
+ description: "Remap value from one range to another",
800
+ inputs: [
801
+ { id: "value", name: "Value", type: "float", defaultValue: 0 },
802
+ { id: "inMin", name: "In Min", type: "float", defaultValue: 0 },
803
+ { id: "inMax", name: "In Max", type: "float", defaultValue: 1 },
804
+ { id: "outMin", name: "Out Min", type: "float", defaultValue: 0 },
805
+ { id: "outMax", name: "Out Max", type: "float", defaultValue: 1 }
806
+ ],
807
+ outputs: [{ id: "result", name: "Result", type: "float" }],
808
+ generateCode: (_, inputs) => {
809
+ return `(${inputs.outMin} + (${inputs.value} - ${inputs.inMin}) * (${inputs.outMax} - ${inputs.outMin}) / (${inputs.inMax} - ${inputs.inMin}))`;
810
+ }
811
+ },
812
+ {
813
+ type: "utility_if",
814
+ name: "Branch",
815
+ category: "utility",
816
+ description: "Conditional selection",
817
+ inputs: [
818
+ { id: "condition", name: "Condition", type: "float", defaultValue: 0 },
819
+ { id: "true", name: "True", type: "float", defaultValue: 1 },
820
+ { id: "false", name: "False", type: "float", defaultValue: 0 }
821
+ ],
822
+ outputs: [{ id: "result", name: "Result", type: "float" }],
823
+ generateCode: (_, inputs) => `select(${inputs.false}, ${inputs.true}, ${inputs.condition} > 0.5)`
824
+ },
825
+ {
826
+ type: "utility_compare",
827
+ name: "Compare",
828
+ category: "utility",
829
+ description: "Compare two values",
830
+ inputs: [
831
+ { id: "a", name: "A", type: "float", defaultValue: 0 },
832
+ { id: "b", name: "B", type: "float", defaultValue: 0 }
833
+ ],
834
+ outputs: [
835
+ { id: "equal", name: "A == B", type: "float" },
836
+ { id: "less", name: "A < B", type: "float" },
837
+ { id: "greater", name: "A > B", type: "float" }
838
+ ],
839
+ generateCode: (_, inputs) => `compareValues(${inputs.a}, ${inputs.b})`
840
+ }
841
+ ];
842
+ var ADVANCED_MATERIAL_NODES = [
843
+ {
844
+ type: "blackbody",
845
+ name: "Blackbody Radiation",
846
+ category: "material",
847
+ description: "Convert temperature (Kelvin) to emission color via Planck approximation",
848
+ inputs: [
849
+ { id: "temperature", name: "Temperature K", type: "float", defaultValue: 5500 },
850
+ { id: "intensity", name: "Intensity", type: "float", defaultValue: 1 }
851
+ ],
852
+ outputs: [{ id: "color", name: "Color", type: "vec3" }],
853
+ generateCode: (_, inputs) => `blackbodyColor(${inputs.temperature}, ${inputs.intensity})`
854
+ },
855
+ {
856
+ type: "sparkle",
857
+ name: "Sparkle / Glitter",
858
+ category: "material",
859
+ description: "Stochastic micro-facet flashing for glitter, metallic paint, mica",
860
+ inputs: [
861
+ { id: "uv", name: "UV", type: "vec2", defaultValue: [0, 0] },
862
+ { id: "density", name: "Density", type: "float", defaultValue: 50 },
863
+ { id: "size", name: "Size", type: "float", defaultValue: 0.02 },
864
+ { id: "intensity", name: "Intensity", type: "float", defaultValue: 1 },
865
+ { id: "viewDir", name: "View Dir", type: "vec3", defaultValue: [0, 0, 1] },
866
+ { id: "normal", name: "Normal", type: "vec3", defaultValue: [0, 0, 1] }
867
+ ],
868
+ outputs: [
869
+ { id: "flash", name: "Flash", type: "float" },
870
+ { id: "color", name: "Color", type: "vec3" }
871
+ ],
872
+ generateCode: (_, inputs) => `sparkleFlash(${inputs.uv}, ${inputs.density}, ${inputs.size}, ${inputs.intensity}, ${inputs.viewDir}, ${inputs.normal})`
873
+ },
874
+ {
875
+ type: "animated_pattern",
876
+ name: "Animated Pattern",
877
+ category: "material",
878
+ description: "Time-varying surface patterns: ripple, flicker, pulse, flow, wave, noise",
879
+ inputs: [
880
+ { id: "uv", name: "UV", type: "vec2", defaultValue: [0, 0] },
881
+ { id: "time", name: "Time", type: "float", defaultValue: 0 },
882
+ { id: "patternType", name: "Pattern (0-7)", type: "float", defaultValue: 0 },
883
+ { id: "speed", name: "Speed", type: "float", defaultValue: 1 },
884
+ { id: "amplitude", name: "Amplitude", type: "float", defaultValue: 1 },
885
+ { id: "scale", name: "Scale", type: "float", defaultValue: 1 }
886
+ ],
887
+ outputs: [
888
+ { id: "value", name: "Value", type: "float" },
889
+ { id: "offset", name: "UV Offset", type: "vec2" }
890
+ ],
891
+ generateCode: (_, inputs) => `animatedPattern(${inputs.uv}, ${inputs.time}, ${inputs.patternType}, ${inputs.speed}, ${inputs.amplitude}, ${inputs.scale})`
892
+ },
893
+ {
894
+ type: "weathering",
895
+ name: "Weathering",
896
+ category: "material",
897
+ description: "Procedural surface degradation: rust, moss, crack, peel, patina, frost",
898
+ inputs: [
899
+ { id: "uv", name: "UV", type: "vec2", defaultValue: [0, 0] },
900
+ { id: "progress", name: "Progress", type: "float", defaultValue: 0 },
901
+ { id: "weatherType", name: "Type (0-9)", type: "float", defaultValue: 0 },
902
+ { id: "seed", name: "Seed", type: "float", defaultValue: 42 },
903
+ { id: "baseColor", name: "Base Color", type: "vec3", defaultValue: [1, 1, 1] }
904
+ ],
905
+ outputs: [
906
+ { id: "color", name: "Weathered Color", type: "vec3" },
907
+ { id: "mask", name: "Mask", type: "float" },
908
+ { id: "roughnessMod", name: "Roughness Mod", type: "float" }
909
+ ],
910
+ generateCode: (_, inputs) => `weatheringSurface(${inputs.uv}, ${inputs.progress}, ${inputs.weatherType}, ${inputs.seed}, ${inputs.baseColor})`
911
+ },
912
+ {
913
+ type: "dual_layer_blend",
914
+ name: "Dual Layer Blend",
915
+ category: "material",
916
+ description: "Blend two material layers (paint over rust, moss over stone, snow on branches)",
917
+ inputs: [
918
+ { id: "baseColor", name: "Base Color", type: "vec3", defaultValue: [0.5, 0.5, 0.5] },
919
+ { id: "baseRoughness", name: "Base Rough", type: "float", defaultValue: 0.5 },
920
+ { id: "baseMetallic", name: "Base Metal", type: "float", defaultValue: 0 },
921
+ { id: "topColor", name: "Top Color", type: "vec3", defaultValue: [1, 1, 1] },
922
+ { id: "topRoughness", name: "Top Rough", type: "float", defaultValue: 0.5 },
923
+ { id: "topMetallic", name: "Top Metal", type: "float", defaultValue: 0 },
924
+ { id: "blendFactor", name: "Blend", type: "float", defaultValue: 0.5 },
925
+ { id: "blendMode", name: "Mode (0-3)", type: "float", defaultValue: 0 },
926
+ { id: "normal", name: "Normal", type: "vec3", defaultValue: [0, 0, 1] },
927
+ { id: "uv", name: "UV", type: "vec2", defaultValue: [0, 0] }
928
+ ],
929
+ outputs: [
930
+ { id: "color", name: "Color", type: "vec3" },
931
+ { id: "roughness", name: "Roughness", type: "float" },
932
+ { id: "metallic", name: "Metallic", type: "float" }
933
+ ],
934
+ generateCode: (_, inputs) => `dualLayerBlend(${inputs.baseColor}, ${inputs.baseRoughness}, ${inputs.baseMetallic}, ${inputs.topColor}, ${inputs.topRoughness}, ${inputs.topMetallic}, ${inputs.blendFactor}, ${inputs.blendMode}, ${inputs.normal}, ${inputs.uv})`
935
+ },
936
+ {
937
+ type: "fluorescence",
938
+ name: "Fluorescence",
939
+ category: "material",
940
+ description: "UV-reactive emission: absorb short wavelengths, emit longer ones",
941
+ inputs: [
942
+ { id: "lightColor", name: "Light Color", type: "vec3", defaultValue: [1, 1, 1] },
943
+ { id: "excitationColor", name: "Excitation", type: "vec3", defaultValue: [0.2, 0, 0.5] },
944
+ { id: "emissionColor", name: "Emission", type: "vec3", defaultValue: [0, 1, 0] },
945
+ { id: "intensity", name: "Intensity", type: "float", defaultValue: 1 }
946
+ ],
947
+ outputs: [{ id: "emission", name: "Emission", type: "vec3" }],
948
+ generateCode: (_, inputs) => `fluorescenceEmit(${inputs.lightColor}, ${inputs.excitationColor}, ${inputs.emissionColor}, ${inputs.intensity})`
949
+ },
950
+ {
951
+ type: "retroreflection",
952
+ name: "Retroreflection",
953
+ category: "material",
954
+ description: "Light bounces back toward source (road signs, safety vests, cat eyes)",
955
+ inputs: [
956
+ { id: "viewDir", name: "View Dir", type: "vec3", defaultValue: [0, 0, 1] },
957
+ { id: "lightDir", name: "Light Dir", type: "vec3", defaultValue: [0, 1, 0] },
958
+ { id: "normal", name: "Normal", type: "vec3", defaultValue: [0, 0, 1] },
959
+ { id: "intensity", name: "Intensity", type: "float", defaultValue: 1 }
960
+ ],
961
+ outputs: [{ id: "reflectance", name: "Reflectance", type: "float" }],
962
+ generateCode: (_, inputs) => `retroReflect(${inputs.viewDir}, ${inputs.lightDir}, ${inputs.normal}, ${inputs.intensity})`
963
+ }
964
+ ];
965
+ var VOLUMETRIC_NODES = [
966
+ {
967
+ type: "volume_density",
968
+ name: "Volume Density",
969
+ category: "volumetric",
970
+ description: "Sample/generate volume density at a 3D position",
971
+ inputs: [
972
+ { id: "position", name: "Position", type: "vec3", defaultValue: [0, 0, 0] },
973
+ { id: "baseDensity", name: "Base Density", type: "float", defaultValue: 0.5 },
974
+ { id: "noiseScale", name: "Noise Scale", type: "float", defaultValue: 4 },
975
+ { id: "noiseOctaves", name: "Octaves", type: "float", defaultValue: 4 },
976
+ { id: "time", name: "Time", type: "float", defaultValue: 0 }
977
+ ],
978
+ outputs: [{ id: "density", name: "Density", type: "float" }],
979
+ generateCode: (_, inputs) => `volumeDensity(${inputs.position}, ${inputs.baseDensity}, ${inputs.noiseScale}, ${inputs.noiseOctaves}, ${inputs.time})`
980
+ },
981
+ {
982
+ type: "volume_noise_3d",
983
+ name: "3D FBM Noise",
984
+ category: "volumetric",
985
+ description: "Fractal Brownian motion noise in 3D for volumetric density",
986
+ inputs: [
987
+ { id: "position", name: "Position", type: "vec3", defaultValue: [0, 0, 0] },
988
+ { id: "octaves", name: "Octaves", type: "float", defaultValue: 4 },
989
+ { id: "lacunarity", name: "Lacunarity", type: "float", defaultValue: 2 },
990
+ { id: "gain", name: "Gain", type: "float", defaultValue: 0.5 }
991
+ ],
992
+ outputs: [{ id: "noise", name: "Noise", type: "float" }],
993
+ generateCode: (_, inputs) => `fbmNoise3D(${inputs.position}, i32(${inputs.octaves}), ${inputs.lacunarity}, ${inputs.gain})`
994
+ },
995
+ {
996
+ type: "volume_curl_noise",
997
+ name: "Curl Noise 3D",
998
+ category: "volumetric",
999
+ description: "Divergence-free curl noise for smoke/fire turbulence",
1000
+ inputs: [
1001
+ { id: "position", name: "Position", type: "vec3", defaultValue: [0, 0, 0] },
1002
+ { id: "scale", name: "Scale", type: "float", defaultValue: 1 }
1003
+ ],
1004
+ outputs: [{ id: "curl", name: "Curl", type: "vec3" }],
1005
+ generateCode: (_, inputs) => `curlNoise3D(${inputs.position} * ${inputs.scale})`
1006
+ },
1007
+ {
1008
+ type: "volume_scatter",
1009
+ name: "Volume Scattering",
1010
+ category: "volumetric",
1011
+ description: "Beer-Lambert absorption + Henyey-Greenstein phase function",
1012
+ inputs: [
1013
+ { id: "density", name: "Density", type: "float", defaultValue: 0.5 },
1014
+ { id: "scattering", name: "Scattering", type: "float", defaultValue: 0.5 },
1015
+ { id: "absorption", name: "Absorption", type: "float", defaultValue: 0.1 },
1016
+ { id: "stepLength", name: "Step Length", type: "float", defaultValue: 0.1 },
1017
+ { id: "lightDir", name: "Light Dir", type: "vec3", defaultValue: [0, 1, 0] },
1018
+ { id: "viewDir", name: "View Dir", type: "vec3", defaultValue: [0, 0, 1] },
1019
+ { id: "anisotropy", name: "Phase Aniso", type: "float", defaultValue: 0.3 }
1020
+ ],
1021
+ outputs: [
1022
+ { id: "transmittance", name: "Transmittance", type: "float" },
1023
+ { id: "inScatter", name: "In-Scatter", type: "float" }
1024
+ ],
1025
+ generateCode: (_, inputs) => `volumeScatter(${inputs.density}, ${inputs.scattering}, ${inputs.absorption}, ${inputs.stepLength}, ${inputs.lightDir}, ${inputs.viewDir}, ${inputs.anisotropy})`
1026
+ },
1027
+ {
1028
+ type: "volume_emission",
1029
+ name: "Volume Emission",
1030
+ category: "volumetric",
1031
+ description: "Self-luminous volume contribution (fire, neon gas, aurora)",
1032
+ inputs: [
1033
+ { id: "density", name: "Density", type: "float", defaultValue: 0.5 },
1034
+ { id: "emissionColor", name: "Emission Color", type: "vec3", defaultValue: [1, 0.5, 0] },
1035
+ { id: "intensity", name: "Intensity", type: "float", defaultValue: 1 },
1036
+ { id: "temperature", name: "Temperature K", type: "float", defaultValue: 0 }
1037
+ ],
1038
+ outputs: [{ id: "emission", name: "Emission", type: "vec3" }],
1039
+ generateCode: (_, inputs) => `volumeEmission(${inputs.density}, ${inputs.emissionColor}, ${inputs.intensity}, ${inputs.temperature})`
1040
+ },
1041
+ {
1042
+ type: "volume_height_fog",
1043
+ name: "Height Fog",
1044
+ category: "volumetric",
1045
+ description: "Height-attenuated fog density with exponential falloff",
1046
+ inputs: [
1047
+ { id: "position", name: "Position", type: "vec3", defaultValue: [0, 0, 0] },
1048
+ { id: "groundLevel", name: "Ground Y", type: "float", defaultValue: 0 },
1049
+ { id: "density", name: "Density", type: "float", defaultValue: 0.5 },
1050
+ { id: "falloff", name: "Falloff", type: "float", defaultValue: 2 }
1051
+ ],
1052
+ outputs: [{ id: "density", name: "Density", type: "float" }],
1053
+ generateCode: (_, inputs) => `heightFogDensity(${inputs.position}, ${inputs.groundLevel}, ${inputs.density}, ${inputs.falloff})`
1054
+ },
1055
+ {
1056
+ type: "volume_fire_density",
1057
+ name: "Fire Density",
1058
+ category: "volumetric",
1059
+ description: "Fire-specific density: buoyancy, turbulence, temperature \u2192 emission",
1060
+ inputs: [
1061
+ { id: "position", name: "Position", type: "vec3", defaultValue: [0, 0, 0] },
1062
+ { id: "time", name: "Time", type: "float", defaultValue: 0 },
1063
+ { id: "turbulence", name: "Turbulence", type: "float", defaultValue: 0.5 },
1064
+ { id: "riseSpeed", name: "Rise Speed", type: "float", defaultValue: 1 },
1065
+ { id: "scale", name: "Scale", type: "float", defaultValue: 2 }
1066
+ ],
1067
+ outputs: [
1068
+ { id: "density", name: "Density", type: "float" },
1069
+ { id: "temperature", name: "Temperature", type: "float" }
1070
+ ],
1071
+ generateCode: (_, inputs) => `fireDensity(${inputs.position}, ${inputs.time}, ${inputs.turbulence}, ${inputs.riseSpeed}, ${inputs.scale})`
1072
+ }
1073
+ ];
1074
+ var OUTPUT_NODES = [
1075
+ {
1076
+ type: "output_surface",
1077
+ name: "Surface Output",
1078
+ category: "output",
1079
+ description: "PBR surface output with SSS, sheen, anisotropy, clearcoat, iridescence",
1080
+ inputs: [
1081
+ { id: "baseColor", name: "Base Color", type: "vec3", defaultValue: [1, 1, 1] },
1082
+ { id: "metallic", name: "Metallic", type: "float", defaultValue: 0 },
1083
+ { id: "roughness", name: "Roughness", type: "float", defaultValue: 0.5 },
1084
+ { id: "normal", name: "Normal", type: "vec3", defaultValue: [0, 0, 1] },
1085
+ { id: "emission", name: "Emission", type: "vec3", defaultValue: [0, 0, 0] },
1086
+ { id: "alpha", name: "Alpha", type: "float", defaultValue: 1 },
1087
+ { id: "ao", name: "AO", type: "float", defaultValue: 1 },
1088
+ // Advanced PBR
1089
+ { id: "sheenColor", name: "Sheen Color", type: "vec3", defaultValue: [0, 0, 0] },
1090
+ { id: "sheenRoughness", name: "Sheen Roughness", type: "float", defaultValue: 0 },
1091
+ { id: "subsurfaceThickness", name: "SSS Thickness", type: "float", defaultValue: 0 },
1092
+ { id: "subsurfaceColor", name: "SSS Color", type: "vec3", defaultValue: [1, 1, 1] },
1093
+ { id: "anisotropy", name: "Anisotropy", type: "float", defaultValue: 0 },
1094
+ { id: "anisotropyRotation", name: "Aniso Rotation", type: "float", defaultValue: 0 },
1095
+ { id: "clearcoat", name: "Clearcoat", type: "float", defaultValue: 0 },
1096
+ { id: "clearcoatRoughness", name: "Clearcoat Rough", type: "float", defaultValue: 0 },
1097
+ { id: "iridescence", name: "Iridescence", type: "float", defaultValue: 0 },
1098
+ // Exotic optics
1099
+ { id: "sparkleIntensity", name: "Sparkle", type: "float", defaultValue: 0 },
1100
+ { id: "sparkleDensity", name: "Sparkle Density", type: "float", defaultValue: 0 },
1101
+ { id: "fluorescenceColor", name: "Fluorescence", type: "vec3", defaultValue: [0, 0, 0] },
1102
+ { id: "fluorescenceIntensity", name: "Fluor Intensity", type: "float", defaultValue: 0 },
1103
+ { id: "blackbodyTemp", name: "Blackbody K", type: "float", defaultValue: 0 },
1104
+ { id: "retroreflection", name: "Retroreflect", type: "float", defaultValue: 0 }
1105
+ ],
1106
+ outputs: [],
1107
+ generateCode: (_, inputs) => {
1108
+ return `SurfaceOutput(
1109
+ ${inputs.baseColor},
1110
+ ${inputs.metallic},
1111
+ ${inputs.roughness},
1112
+ ${inputs.normal},
1113
+ ${inputs.emission},
1114
+ ${inputs.alpha},
1115
+ ${inputs.ao},
1116
+ ${inputs.sheenColor},
1117
+ ${inputs.sheenRoughness},
1118
+ ${inputs.subsurfaceThickness},
1119
+ ${inputs.subsurfaceColor},
1120
+ ${inputs.anisotropy},
1121
+ ${inputs.anisotropyRotation},
1122
+ ${inputs.clearcoat},
1123
+ ${inputs.clearcoatRoughness},
1124
+ ${inputs.iridescence},
1125
+ ${inputs.sparkleIntensity},
1126
+ ${inputs.sparkleDensity},
1127
+ ${inputs.fluorescenceColor},
1128
+ ${inputs.fluorescenceIntensity},
1129
+ ${inputs.blackbodyTemp},
1130
+ ${inputs.retroreflection}
1131
+ )`;
1132
+ }
1133
+ },
1134
+ {
1135
+ type: "output_unlit",
1136
+ name: "Unlit Output",
1137
+ category: "output",
1138
+ description: "Unlit color output",
1139
+ inputs: [{ id: "color", name: "Color", type: "vec4", defaultValue: [1, 1, 1, 1] }],
1140
+ outputs: [],
1141
+ generateCode: (_, inputs) => `${inputs.color}`
1142
+ },
1143
+ {
1144
+ type: "output_vertex_offset",
1145
+ name: "Vertex Offset",
1146
+ category: "output",
1147
+ description: "Vertex position offset",
1148
+ inputs: [{ id: "offset", name: "Offset", type: "vec3", defaultValue: [0, 0, 0] }],
1149
+ outputs: [],
1150
+ generateCode: (_, inputs) => inputs.offset
1151
+ },
1152
+ {
1153
+ type: "output_volume",
1154
+ name: "Volume Output",
1155
+ category: "output",
1156
+ description: "Volumetric output \u2014 ray-marched density, color, emission, scattering",
1157
+ inputs: [
1158
+ { id: "density", name: "Density", type: "float", defaultValue: 0.5 },
1159
+ { id: "color", name: "Color", type: "vec3", defaultValue: [1, 1, 1] },
1160
+ { id: "emission", name: "Emission", type: "vec3", defaultValue: [0, 0, 0] },
1161
+ { id: "scattering", name: "Scattering", type: "float", defaultValue: 0.5 },
1162
+ { id: "absorption", name: "Absorption", type: "float", defaultValue: 0.1 },
1163
+ { id: "steps", name: "Steps", type: "float", defaultValue: 64 },
1164
+ { id: "maxDistance", name: "Max Dist", type: "float", defaultValue: 10 }
1165
+ ],
1166
+ outputs: [],
1167
+ generateCode: (_, inputs) => {
1168
+ return `VolumeOutput(
1169
+ ${inputs.density},
1170
+ ${inputs.color},
1171
+ ${inputs.emission},
1172
+ ${inputs.scattering},
1173
+ ${inputs.absorption},
1174
+ ${inputs.steps},
1175
+ ${inputs.maxDistance}
1176
+ )`;
1177
+ }
1178
+ }
1179
+ ];
1180
+ var SCREEN_SPACE_NODES = [
1181
+ {
1182
+ type: "caustics",
1183
+ name: "Caustics",
1184
+ category: "material",
1185
+ description: "Underwater caustic light patterns from refracted light",
1186
+ inputs: [
1187
+ { id: "position", name: "Position", type: "vec3", defaultValue: [0, 0, 0] },
1188
+ { id: "time", name: "Time", type: "float", defaultValue: 0 },
1189
+ { id: "scale", name: "Scale", type: "float", defaultValue: 1 },
1190
+ { id: "speed", name: "Speed", type: "float", defaultValue: 1 },
1191
+ { id: "intensity", name: "Intensity", type: "float", defaultValue: 1 },
1192
+ { id: "color", name: "Color", type: "vec3", defaultValue: [0.2, 0.5, 0.8] }
1193
+ ],
1194
+ outputs: [
1195
+ { id: "caustic", name: "Caustic", type: "vec3" },
1196
+ { id: "mask", name: "Mask", type: "float" }
1197
+ ],
1198
+ generateCode: (_, inputs) => `causticPattern(${inputs.position}, ${inputs.time}, ${inputs.scale}, ${inputs.speed}, ${inputs.intensity}, ${inputs.color})`
1199
+ },
1200
+ {
1201
+ type: "displacement_map",
1202
+ name: "Displacement Map",
1203
+ category: "material",
1204
+ description: "Vertex displacement from height/normal map",
1205
+ inputs: [
1206
+ { id: "position", name: "Position", type: "vec3", defaultValue: [0, 0, 0] },
1207
+ { id: "normal", name: "Normal", type: "vec3", defaultValue: [0, 1, 0] },
1208
+ { id: "height", name: "Height", type: "float", defaultValue: 0 },
1209
+ { id: "strength", name: "Strength", type: "float", defaultValue: 1 },
1210
+ { id: "midLevel", name: "Mid Level", type: "float", defaultValue: 0.5 }
1211
+ ],
1212
+ outputs: [{ id: "displaced", name: "Displaced", type: "vec3" }],
1213
+ generateCode: (_, inputs) => `displacementMap(${inputs.position}, ${inputs.normal}, ${inputs.height}, ${inputs.strength}, ${inputs.midLevel})`
1214
+ },
1215
+ {
1216
+ type: "parallax_occlusion",
1217
+ name: "Parallax Occlusion",
1218
+ category: "material",
1219
+ description: "Parallax occlusion mapping for depth illusion without extra geometry",
1220
+ inputs: [
1221
+ { id: "uv", name: "UV", type: "vec2", defaultValue: [0, 0] },
1222
+ { id: "viewDir", name: "View Dir", type: "vec3", defaultValue: [0, 0, 1] },
1223
+ { id: "heightScale", name: "Height Scale", type: "float", defaultValue: 0.05 },
1224
+ { id: "numLayers", name: "Layers", type: "float", defaultValue: 32 }
1225
+ ],
1226
+ outputs: [
1227
+ { id: "adjustedUV", name: "Adjusted UV", type: "vec2" },
1228
+ { id: "depth", name: "Depth", type: "float" }
1229
+ ],
1230
+ generateCode: (_, inputs) => `parallaxOcclusionMap(${inputs.uv}, ${inputs.viewDir}, ${inputs.heightScale}, ${inputs.numLayers})`
1231
+ },
1232
+ {
1233
+ type: "screen_space_reflection",
1234
+ name: "Screen Space Reflection",
1235
+ category: "material",
1236
+ description: "SSR \u2014 ray-marched reflections in screen space",
1237
+ inputs: [
1238
+ { id: "uv", name: "UV", type: "vec2", defaultValue: [0, 0] },
1239
+ { id: "normal", name: "Normal", type: "vec3", defaultValue: [0, 1, 0] },
1240
+ { id: "viewDir", name: "View Dir", type: "vec3", defaultValue: [0, 0, 1] },
1241
+ { id: "roughness", name: "Roughness", type: "float", defaultValue: 0.1 },
1242
+ { id: "maxSteps", name: "Max Steps", type: "float", defaultValue: 64 },
1243
+ { id: "stepSize", name: "Step Size", type: "float", defaultValue: 0.1 }
1244
+ ],
1245
+ outputs: [
1246
+ { id: "reflection", name: "Reflection", type: "vec3" },
1247
+ { id: "hitMask", name: "Hit Mask", type: "float" }
1248
+ ],
1249
+ generateCode: (_, inputs) => `screenSpaceReflect(${inputs.uv}, ${inputs.normal}, ${inputs.viewDir}, ${inputs.roughness}, ${inputs.maxSteps}, ${inputs.stepSize})`
1250
+ },
1251
+ {
1252
+ type: "screen_space_gi",
1253
+ name: "Screen Space GI",
1254
+ category: "material",
1255
+ description: "Approximate global illumination from screen-space data",
1256
+ inputs: [
1257
+ { id: "uv", name: "UV", type: "vec2", defaultValue: [0, 0] },
1258
+ { id: "normal", name: "Normal", type: "vec3", defaultValue: [0, 1, 0] },
1259
+ { id: "radius", name: "Radius", type: "float", defaultValue: 1 },
1260
+ { id: "samples", name: "Samples", type: "float", defaultValue: 16 },
1261
+ { id: "bounceIntensity", name: "Bounce", type: "float", defaultValue: 1 }
1262
+ ],
1263
+ outputs: [{ id: "indirectLight", name: "Indirect", type: "vec3" }],
1264
+ generateCode: (_, inputs) => `screenSpaceGI(${inputs.uv}, ${inputs.normal}, ${inputs.radius}, ${inputs.samples}, ${inputs.bounceIntensity})`
1265
+ },
1266
+ {
1267
+ type: "water_surface",
1268
+ name: "Water Surface",
1269
+ category: "material",
1270
+ description: "Combined water surface effect with waves, refraction, foam, and caustics",
1271
+ inputs: [
1272
+ { id: "position", name: "Position", type: "vec3", defaultValue: [0, 0, 0] },
1273
+ { id: "time", name: "Time", type: "float", defaultValue: 0 },
1274
+ { id: "waveScale", name: "Wave Scale", type: "float", defaultValue: 1 },
1275
+ { id: "waveSpeed", name: "Wave Speed", type: "float", defaultValue: 1 },
1276
+ { id: "depth", name: "Water Depth", type: "float", defaultValue: 5 },
1277
+ { id: "foamThreshold", name: "Foam Thresh", type: "float", defaultValue: 0.7 }
1278
+ ],
1279
+ outputs: [
1280
+ { id: "displacement", name: "Displacement", type: "vec3" },
1281
+ { id: "normal", name: "Normal", type: "vec3" },
1282
+ { id: "foam", name: "Foam", type: "float" },
1283
+ { id: "caustic", name: "Caustic", type: "float" }
1284
+ ],
1285
+ generateCode: (_, inputs) => `waterSurface(${inputs.position}, ${inputs.time}, ${inputs.waveScale}, ${inputs.waveSpeed}, ${inputs.depth}, ${inputs.foamThreshold})`
1286
+ },
1287
+ {
1288
+ type: "caustics_refractive",
1289
+ name: "Refractive Caustics",
1290
+ category: "material",
1291
+ description: "Chromatic refractive caustics with RGB dispersion for prismatic light patterns",
1292
+ inputs: [
1293
+ { id: "position", name: "Position", type: "vec3", defaultValue: [0, 0, 0] },
1294
+ { id: "time", name: "Time", type: "float", defaultValue: 0 },
1295
+ { id: "scale", name: "Scale", type: "float", defaultValue: 1 },
1296
+ { id: "speed", name: "Speed", type: "float", defaultValue: 1 },
1297
+ { id: "intensity", name: "Intensity", type: "float", defaultValue: 1 },
1298
+ { id: "dispersion", name: "Dispersion", type: "float", defaultValue: 0.05 }
1299
+ ],
1300
+ outputs: [
1301
+ { id: "caustic", name: "Caustic", type: "vec3" },
1302
+ { id: "mask", name: "Mask", type: "float" }
1303
+ ],
1304
+ generateCode: (_, inputs) => `causticChromatic(${inputs.position}, ${inputs.time}, ${inputs.scale}, ${inputs.speed}, ${inputs.intensity}, ${inputs.dispersion})`
1305
+ },
1306
+ {
1307
+ type: "displacement_enhanced",
1308
+ name: "Enhanced Displacement",
1309
+ category: "material",
1310
+ description: "Displacement with reconstructed normal and optional anisotropic weighting",
1311
+ inputs: [
1312
+ { id: "position", name: "Position", type: "vec3", defaultValue: [0, 0, 0] },
1313
+ { id: "normal", name: "Normal", type: "vec3", defaultValue: [0, 1, 0] },
1314
+ { id: "tangent", name: "Tangent", type: "vec3", defaultValue: [1, 0, 0] },
1315
+ { id: "height", name: "Height", type: "float", defaultValue: 0 },
1316
+ { id: "strength", name: "Strength", type: "float", defaultValue: 1 },
1317
+ { id: "anisotropy", name: "Anisotropy", type: "float", defaultValue: 0 },
1318
+ { id: "midLevel", name: "Mid Level", type: "float", defaultValue: 0.5 }
1319
+ ],
1320
+ outputs: [
1321
+ { id: "displaced", name: "Displaced", type: "vec3" },
1322
+ { id: "reconstructedNormal", name: "Normal", type: "vec3" }
1323
+ ],
1324
+ generateCode: (_, inputs) => `anisotropicDisplacement(${inputs.position}, ${inputs.normal}, ${inputs.tangent}, ${inputs.height}, ${inputs.strength}, ${inputs.anisotropy}, ${inputs.midLevel})`
1325
+ },
1326
+ {
1327
+ type: "underwater_foam",
1328
+ name: "Underwater Foam",
1329
+ category: "material",
1330
+ description: "Procedural underwater foam patches driven by turbulence",
1331
+ inputs: [
1332
+ { id: "position", name: "Position", type: "vec3", defaultValue: [0, 0, 0] },
1333
+ { id: "time", name: "Time", type: "float", defaultValue: 0 },
1334
+ { id: "scale", name: "Scale", type: "float", defaultValue: 1 },
1335
+ { id: "threshold", name: "Threshold", type: "float", defaultValue: 0.6 }
1336
+ ],
1337
+ outputs: [{ id: "foam", name: "Foam", type: "float" }],
1338
+ generateCode: (_, inputs) => `underwaterFoam(${inputs.position}, ${inputs.time}, ${inputs.scale}, ${inputs.threshold})`
1339
+ }
1340
+ ];
1341
+ var ALL_NODE_TEMPLATES = [
1342
+ ...INPUT_NODES,
1343
+ ...MATH_NODES,
1344
+ ...TRIG_NODES,
1345
+ ...VECTOR_NODES,
1346
+ ...COLOR_NODES,
1347
+ ...TEXTURE_NODES,
1348
+ ...UTILITY_NODES,
1349
+ ...ADVANCED_MATERIAL_NODES,
1350
+ ...VOLUMETRIC_NODES,
1351
+ ...SCREEN_SPACE_NODES,
1352
+ ...OUTPUT_NODES
1353
+ ];
1354
+ function getNodeTemplate(type) {
1355
+ return ALL_NODE_TEMPLATES.find((t) => t.type === type);
1356
+ }
1357
+
1358
+ // src/shader/graph/ShaderGraph.ts
1359
+ var ShaderGraph = class _ShaderGraph {
1360
+ id;
1361
+ name;
1362
+ description;
1363
+ nodes;
1364
+ connections;
1365
+ version = "1.0.0";
1366
+ metadata;
1367
+ nodeCounter = 0;
1368
+ connectionCounter = 0;
1369
+ constructor(name, id) {
1370
+ this.id = id ?? this.generateId("graph");
1371
+ this.name = name ?? "Untitled Shader";
1372
+ this.nodes = /* @__PURE__ */ new Map();
1373
+ this.connections = [];
1374
+ }
1375
+ /**
1376
+ * Generate unique ID
1377
+ */
1378
+ generateId(prefix) {
1379
+ return `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
1380
+ }
1381
+ /**
1382
+ * Create a node from a template
1383
+ */
1384
+ createNode(type, position = { x: 0, y: 0 }) {
1385
+ const template = getNodeTemplate(type);
1386
+ if (!template) {
1387
+ console.warn(`Unknown node type: ${type}`);
1388
+ return null;
1389
+ }
1390
+ const nodeId = `node_${++this.nodeCounter}`;
1391
+ const node = {
1392
+ id: nodeId,
1393
+ type: template.type,
1394
+ name: template.name,
1395
+ category: template.category,
1396
+ inputs: template.inputs.map((input) => ({
1397
+ ...input,
1398
+ direction: "input"
1399
+ })),
1400
+ outputs: template.outputs.map((output) => ({
1401
+ ...output,
1402
+ direction: "output"
1403
+ })),
1404
+ position,
1405
+ properties: template.defaultProperties ? { ...template.defaultProperties } : void 0
1406
+ };
1407
+ this.nodes.set(nodeId, node);
1408
+ return node;
1409
+ }
1410
+ /**
1411
+ * Add a custom node
1412
+ */
1413
+ addCustomNode(node) {
1414
+ const nodeId = `node_${++this.nodeCounter}`;
1415
+ const fullNode = {
1416
+ ...node,
1417
+ id: nodeId
1418
+ };
1419
+ this.nodes.set(nodeId, fullNode);
1420
+ return fullNode;
1421
+ }
1422
+ /**
1423
+ * Get a node by ID
1424
+ */
1425
+ getNode(nodeId) {
1426
+ return this.nodes.get(nodeId);
1427
+ }
1428
+ /**
1429
+ * Remove a node by ID
1430
+ */
1431
+ removeNode(nodeId) {
1432
+ const node = this.nodes.get(nodeId);
1433
+ if (!node) return false;
1434
+ this.connections = this.connections.filter(
1435
+ (conn) => conn.fromNode !== nodeId && conn.toNode !== nodeId
1436
+ );
1437
+ return this.nodes.delete(nodeId);
1438
+ }
1439
+ /**
1440
+ * Update node position
1441
+ */
1442
+ setNodePosition(nodeId, x, y) {
1443
+ const node = this.nodes.get(nodeId);
1444
+ if (!node) return false;
1445
+ node.position = { x, y };
1446
+ return true;
1447
+ }
1448
+ /**
1449
+ * Update node property
1450
+ */
1451
+ setNodeProperty(nodeId, key, value) {
1452
+ const node = this.nodes.get(nodeId);
1453
+ if (!node) return false;
1454
+ if (!node.properties) node.properties = {};
1455
+ node.properties[key] = value;
1456
+ return true;
1457
+ }
1458
+ /**
1459
+ * Get node property
1460
+ */
1461
+ getNodeProperty(nodeId, key) {
1462
+ const node = this.nodes.get(nodeId);
1463
+ return node?.properties?.[key];
1464
+ }
1465
+ /**
1466
+ * Connect two ports
1467
+ */
1468
+ connect(fromNodeId, fromPortId, toNodeId, toPortId) {
1469
+ const fromNode = this.nodes.get(fromNodeId);
1470
+ const toNode = this.nodes.get(toNodeId);
1471
+ if (!fromNode || !toNode) {
1472
+ console.warn("Invalid node IDs for connection");
1473
+ return null;
1474
+ }
1475
+ const fromPort = fromNode.outputs.find((p) => p.id === fromPortId);
1476
+ const toPort = toNode.inputs.find((p) => p.id === toPortId);
1477
+ if (!fromPort || !toPort) {
1478
+ console.warn("Invalid port IDs for connection");
1479
+ return null;
1480
+ }
1481
+ if (!areTypesCompatible(fromPort.type, toPort.type)) {
1482
+ console.warn(`Type mismatch: ${fromPort.type} -> ${toPort.type}`);
1483
+ return null;
1484
+ }
1485
+ const existingIdx = this.connections.findIndex(
1486
+ (c) => c.toNode === toNodeId && c.toPort === toPortId
1487
+ );
1488
+ if (existingIdx >= 0) {
1489
+ const existing = this.connections[existingIdx];
1490
+ this.disconnectPort(existing.toNode, existing.toPort);
1491
+ this.connections.splice(existingIdx, 1);
1492
+ }
1493
+ if (this.wouldCreateCycle(fromNodeId, toNodeId)) {
1494
+ console.warn("Connection would create a cycle");
1495
+ return null;
1496
+ }
1497
+ const connection = {
1498
+ id: `conn_${++this.connectionCounter}`,
1499
+ fromNode: fromNodeId,
1500
+ fromPort: fromPortId,
1501
+ toNode: toNodeId,
1502
+ toPort: toPortId
1503
+ };
1504
+ this.connections.push(connection);
1505
+ toPort.connected = {
1506
+ nodeId: fromNodeId,
1507
+ portId: fromPortId
1508
+ };
1509
+ return connection;
1510
+ }
1511
+ /**
1512
+ * Disconnect a port
1513
+ */
1514
+ disconnectPort(nodeId, portId) {
1515
+ const node = this.nodes.get(nodeId);
1516
+ if (!node) return false;
1517
+ const inputPort = node.inputs.find((p) => p.id === portId);
1518
+ if (inputPort) {
1519
+ inputPort.connected = void 0;
1520
+ }
1521
+ const initialLength = this.connections.length;
1522
+ this.connections = this.connections.filter(
1523
+ (c) => !(c.toNode === nodeId && c.toPort === portId) && !(c.fromNode === nodeId && c.fromPort === portId)
1524
+ );
1525
+ return this.connections.length !== initialLength;
1526
+ }
1527
+ /**
1528
+ * Get all connections for a node
1529
+ */
1530
+ getNodeConnections(nodeId) {
1531
+ return this.connections.filter((c) => c.fromNode === nodeId || c.toNode === nodeId);
1532
+ }
1533
+ /**
1534
+ * Get input connections for a node
1535
+ */
1536
+ getInputConnections(nodeId) {
1537
+ return this.connections.filter((c) => c.toNode === nodeId);
1538
+ }
1539
+ /**
1540
+ * Get output connections for a node
1541
+ */
1542
+ getOutputConnections(nodeId) {
1543
+ return this.connections.filter((c) => c.fromNode === nodeId);
1544
+ }
1545
+ /**
1546
+ * Check if connection would create a cycle
1547
+ */
1548
+ wouldCreateCycle(fromNodeId, toNodeId) {
1549
+ if (fromNodeId === toNodeId) return true;
1550
+ const visited = /* @__PURE__ */ new Set();
1551
+ const queue = [toNodeId];
1552
+ while (queue.length > 0) {
1553
+ const current = queue.shift();
1554
+ if (current === fromNodeId) return true;
1555
+ if (visited.has(current)) continue;
1556
+ visited.add(current);
1557
+ const outputConnections = this.getOutputConnections(current);
1558
+ for (const conn of outputConnections) {
1559
+ queue.push(conn.toNode);
1560
+ }
1561
+ }
1562
+ return false;
1563
+ }
1564
+ /**
1565
+ * Get nodes by category
1566
+ */
1567
+ getNodesByCategory(category) {
1568
+ return Array.from(this.nodes.values()).filter((n) => n.category === category);
1569
+ }
1570
+ /**
1571
+ * Get all output nodes
1572
+ */
1573
+ getOutputNodes() {
1574
+ return this.getNodesByCategory("output");
1575
+ }
1576
+ /**
1577
+ * Get topologically sorted nodes (dependency order)
1578
+ */
1579
+ getTopologicalOrder() {
1580
+ const sorted = [];
1581
+ const visited = /* @__PURE__ */ new Set();
1582
+ const visiting = /* @__PURE__ */ new Set();
1583
+ const visit = (nodeId) => {
1584
+ if (visited.has(nodeId)) return true;
1585
+ if (visiting.has(nodeId)) return false;
1586
+ visiting.add(nodeId);
1587
+ const node = this.nodes.get(nodeId);
1588
+ if (!node) return true;
1589
+ const inputConns = this.getInputConnections(nodeId);
1590
+ for (const conn of inputConns) {
1591
+ if (!visit(conn.fromNode)) return false;
1592
+ }
1593
+ visiting.delete(nodeId);
1594
+ visited.add(nodeId);
1595
+ sorted.push(node);
1596
+ return true;
1597
+ };
1598
+ const outputNodes = this.getOutputNodes();
1599
+ for (const output of outputNodes) {
1600
+ visit(output.id);
1601
+ }
1602
+ for (const node of this.nodes.values()) {
1603
+ visit(node.id);
1604
+ }
1605
+ return sorted;
1606
+ }
1607
+ /**
1608
+ * Validate the graph
1609
+ */
1610
+ validate() {
1611
+ const errors = [];
1612
+ const warnings = [];
1613
+ const outputNodes = this.getOutputNodes();
1614
+ if (outputNodes.length === 0) {
1615
+ errors.push("Graph has no output nodes");
1616
+ }
1617
+ for (const node of this.nodes.values()) {
1618
+ for (const input of node.inputs) {
1619
+ if (input.required && !input.connected && input.defaultValue === void 0) {
1620
+ errors.push(
1621
+ `Node "${node.name}" (${node.id}): Required input "${input.name}" is not connected`
1622
+ );
1623
+ }
1624
+ }
1625
+ }
1626
+ for (const node of this.nodes.values()) {
1627
+ if (node.category !== "output") {
1628
+ const connections = this.getNodeConnections(node.id);
1629
+ if (connections.length === 0) {
1630
+ warnings.push(`Node "${node.name}" (${node.id}) is not connected to anything`);
1631
+ }
1632
+ }
1633
+ }
1634
+ return {
1635
+ valid: errors.length === 0,
1636
+ errors,
1637
+ warnings
1638
+ };
1639
+ }
1640
+ /**
1641
+ * Clear the graph
1642
+ */
1643
+ clear() {
1644
+ this.nodes.clear();
1645
+ this.connections = [];
1646
+ this.nodeCounter = 0;
1647
+ this.connectionCounter = 0;
1648
+ }
1649
+ /**
1650
+ * Duplicate a node
1651
+ */
1652
+ duplicateNode(nodeId, offset = { x: 50, y: 50 }) {
1653
+ const original = this.nodes.get(nodeId);
1654
+ if (!original) return null;
1655
+ const duplicate = {
1656
+ type: original.type,
1657
+ name: original.name,
1658
+ category: original.category,
1659
+ inputs: original.inputs.map((i) => ({ ...i, connected: void 0 })),
1660
+ outputs: original.outputs.map((o) => ({ ...o })),
1661
+ position: {
1662
+ x: original.position.x + offset.x,
1663
+ y: original.position.y + offset.y
1664
+ },
1665
+ properties: original.properties ? { ...original.properties } : void 0,
1666
+ preview: original.preview
1667
+ };
1668
+ return this.addCustomNode(duplicate);
1669
+ }
1670
+ /**
1671
+ * Get available node templates
1672
+ */
1673
+ static getAvailableNodeTemplates() {
1674
+ return ALL_NODE_TEMPLATES;
1675
+ }
1676
+ /**
1677
+ * Get node templates by category
1678
+ */
1679
+ static getNodeTemplatesByCategory(category) {
1680
+ return ALL_NODE_TEMPLATES.filter((t) => t.category === category);
1681
+ }
1682
+ /**
1683
+ * Serialize to JSON
1684
+ */
1685
+ toJSON() {
1686
+ return {
1687
+ id: this.id,
1688
+ name: this.name,
1689
+ description: this.description,
1690
+ version: this.version,
1691
+ metadata: this.metadata,
1692
+ nodes: Array.from(this.nodes.entries()).map(([id, node]) => {
1693
+ const { id: _nodeId, ...rest } = node;
1694
+ return {
1695
+ id,
1696
+ ...rest
1697
+ };
1698
+ }),
1699
+ connections: this.connections
1700
+ };
1701
+ }
1702
+ /**
1703
+ * Deserialize from JSON
1704
+ */
1705
+ static fromJSON(json) {
1706
+ const graph = new _ShaderGraph(json.name, json.id);
1707
+ graph.description = json.description;
1708
+ graph.version = json.version ?? "1.0.0";
1709
+ graph.metadata = json.metadata;
1710
+ for (const nodeData of json.nodes) {
1711
+ graph.nodes.set(nodeData.id, nodeData);
1712
+ }
1713
+ graph.connections = json.connections ?? [];
1714
+ return graph;
1715
+ }
1716
+ /**
1717
+ * Serialize to JSON string
1718
+ */
1719
+ serialize() {
1720
+ return JSON.stringify(this.toJSON(), null, 2);
1721
+ }
1722
+ /**
1723
+ * Deserialize from JSON string
1724
+ */
1725
+ static deserialize(json) {
1726
+ return _ShaderGraph.fromJSON(JSON.parse(json));
1727
+ }
1728
+ };
1729
+
1730
+ // src/shader/graph/ShaderGraphCompiler.ts
1731
+ var ShaderGraphCompiler = class {
1732
+ graph;
1733
+ options;
1734
+ variableCounter = 0;
1735
+ nodeVariables = /* @__PURE__ */ new Map();
1736
+ uniforms = [];
1737
+ textures = [];
1738
+ warnings = [];
1739
+ errors = [];
1740
+ constructor(graph, options) {
1741
+ this.graph = graph;
1742
+ this.options = {
1743
+ target: options?.target ?? "wgsl",
1744
+ optimize: options?.optimize ?? true,
1745
+ debug: options?.debug ?? false,
1746
+ defines: options?.defines ?? {}
1747
+ };
1748
+ }
1749
+ /**
1750
+ * Compile the shader graph
1751
+ */
1752
+ compile() {
1753
+ this.reset();
1754
+ const validation = this.graph.validate();
1755
+ if (!validation.valid) {
1756
+ return {
1757
+ vertexCode: "",
1758
+ fragmentCode: "",
1759
+ uniforms: [],
1760
+ textures: [],
1761
+ warnings: validation.warnings,
1762
+ errors: validation.errors
1763
+ };
1764
+ }
1765
+ this.warnings = validation.warnings;
1766
+ const sortedNodes = this.graph.getTopologicalOrder();
1767
+ const fragmentBody = this.generateFragmentBody(sortedNodes);
1768
+ const vertexBody = this.generateVertexBody(sortedNodes);
1769
+ const vertexCode = this.generateVertexShader(vertexBody);
1770
+ const fragmentCode = this.generateFragmentShader(fragmentBody);
1771
+ return {
1772
+ vertexCode,
1773
+ fragmentCode,
1774
+ uniforms: this.uniforms,
1775
+ textures: this.textures,
1776
+ warnings: this.warnings,
1777
+ errors: this.errors
1778
+ };
1779
+ }
1780
+ /**
1781
+ * Reset compilation state
1782
+ */
1783
+ reset() {
1784
+ this.variableCounter = 0;
1785
+ this.nodeVariables.clear();
1786
+ this.uniforms = [];
1787
+ this.textures = [];
1788
+ this.warnings = [];
1789
+ this.errors = [];
1790
+ }
1791
+ /**
1792
+ * Generate a unique variable name
1793
+ */
1794
+ generateVariable() {
1795
+ return `v_${this.variableCounter++}`;
1796
+ }
1797
+ /**
1798
+ * Get or create variable for a node output
1799
+ */
1800
+ getOutputVariable(nodeId, portId) {
1801
+ if (!this.nodeVariables.has(nodeId)) {
1802
+ this.nodeVariables.set(nodeId, /* @__PURE__ */ new Map());
1803
+ }
1804
+ const nodeVars = this.nodeVariables.get(nodeId);
1805
+ if (!nodeVars.has(portId)) {
1806
+ nodeVars.set(portId, this.generateVariable());
1807
+ }
1808
+ return nodeVars.get(portId);
1809
+ }
1810
+ /**
1811
+ * Get input expression for a node input
1812
+ */
1813
+ getInputExpression(node, portId, connections) {
1814
+ const port = node.inputs.find((p) => p.id === portId);
1815
+ if (!port) {
1816
+ return "0.0";
1817
+ }
1818
+ const connection = connections.find((c) => c.toNode === node.id && c.toPort === portId);
1819
+ if (connection) {
1820
+ const sourceNode = this.graph.getNode(connection.fromNode);
1821
+ const sourcePort = sourceNode?.outputs.find((p) => p.id === connection.fromPort);
1822
+ if (sourceNode && sourcePort) {
1823
+ const varName = this.getOutputVariable(connection.fromNode, connection.fromPort);
1824
+ if (sourcePort.type !== port.type) {
1825
+ return getTypeConversion(sourcePort.type, port.type, varName);
1826
+ }
1827
+ return varName;
1828
+ }
1829
+ }
1830
+ return this.getDefaultValueCode(port.type, port.defaultValue);
1831
+ }
1832
+ /**
1833
+ * Get WGSL code for default value
1834
+ */
1835
+ getDefaultValueCode(type, value) {
1836
+ if (value === void 0) {
1837
+ return this.getZeroValue(type);
1838
+ }
1839
+ if (typeof value === "number") {
1840
+ if (type === "float") return `${value}`;
1841
+ if (type === "int") return `${Math.floor(value)}i`;
1842
+ return `${value}`;
1843
+ }
1844
+ if (Array.isArray(value)) {
1845
+ switch (type) {
1846
+ case "vec2":
1847
+ return `vec2<f32>(${value[0] ?? 0}, ${value[1] ?? 0})`;
1848
+ case "vec3":
1849
+ return `vec3<f32>(${value[0] ?? 0}, ${value[1] ?? 0}, ${value[2] ?? 0})`;
1850
+ case "vec4":
1851
+ return `vec4<f32>(${value[0] ?? 0}, ${value[1] ?? 0}, ${value[2] ?? 0}, ${value[3] ?? 1})`;
1852
+ default:
1853
+ return this.getZeroValue(type);
1854
+ }
1855
+ }
1856
+ return this.getZeroValue(type);
1857
+ }
1858
+ /**
1859
+ * Get zero value for a type
1860
+ */
1861
+ getZeroValue(type) {
1862
+ switch (type) {
1863
+ case "float":
1864
+ return "0.0";
1865
+ case "vec2":
1866
+ return "vec2<f32>(0.0)";
1867
+ case "vec3":
1868
+ return "vec3<f32>(0.0)";
1869
+ case "vec4":
1870
+ return "vec4<f32>(0.0, 0.0, 0.0, 1.0)";
1871
+ case "int":
1872
+ return "0i";
1873
+ case "ivec2":
1874
+ return "vec2<i32>(0)";
1875
+ case "ivec3":
1876
+ return "vec3<i32>(0)";
1877
+ case "ivec4":
1878
+ return "vec4<i32>(0)";
1879
+ case "bool":
1880
+ return "false";
1881
+ case "mat2":
1882
+ return "mat2x2<f32>(1.0, 0.0, 0.0, 1.0)";
1883
+ case "mat3":
1884
+ return "mat3x3<f32>(1.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0)";
1885
+ case "mat4":
1886
+ return "mat4x4<f32>(1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0)";
1887
+ default:
1888
+ return "0.0";
1889
+ }
1890
+ }
1891
+ /**
1892
+ * Get WGSL type name
1893
+ */
1894
+ getWGSLType(type) {
1895
+ switch (type) {
1896
+ case "float":
1897
+ return "f32";
1898
+ case "vec2":
1899
+ return "vec2<f32>";
1900
+ case "vec3":
1901
+ return "vec3<f32>";
1902
+ case "vec4":
1903
+ return "vec4<f32>";
1904
+ case "int":
1905
+ return "i32";
1906
+ case "ivec2":
1907
+ return "vec2<i32>";
1908
+ case "ivec3":
1909
+ return "vec3<i32>";
1910
+ case "ivec4":
1911
+ return "vec4<i32>";
1912
+ case "bool":
1913
+ return "bool";
1914
+ case "mat2":
1915
+ return "mat2x2<f32>";
1916
+ case "mat3":
1917
+ return "mat3x3<f32>";
1918
+ case "mat4":
1919
+ return "mat4x4<f32>";
1920
+ default:
1921
+ return "f32";
1922
+ }
1923
+ }
1924
+ /**
1925
+ * Generate fragment shader body
1926
+ */
1927
+ generateFragmentBody(nodes) {
1928
+ const lines = [];
1929
+ const connections = this.graph.connections;
1930
+ for (const node of nodes) {
1931
+ if (node.category === "output" && node.type !== "output_surface" && node.type !== "output_unlit" && node.type !== "output_volume") {
1932
+ continue;
1933
+ }
1934
+ const template = getNodeTemplate(node.type);
1935
+ if (!template) {
1936
+ this.warnings.push(`No template found for node type: ${node.type}`);
1937
+ continue;
1938
+ }
1939
+ const inputs = {};
1940
+ for (const port of node.inputs) {
1941
+ inputs[port.id] = this.getInputExpression(node, port.id, connections);
1942
+ }
1943
+ const code = template.generateCode(node, inputs);
1944
+ if (node.type === "output_surface") {
1945
+ lines.push(`// Surface output`);
1946
+ lines.push(`let baseColor = ${inputs.baseColor};`);
1947
+ lines.push(`let metallic = ${inputs.metallic};`);
1948
+ lines.push(`let roughness = ${inputs.roughness};`);
1949
+ lines.push(`let normal = ${inputs.normal};`);
1950
+ lines.push(`let emission = ${inputs.emission};`);
1951
+ lines.push(`let alpha = ${inputs.alpha};`);
1952
+ lines.push(`let ao = ${inputs.ao};`);
1953
+ lines.push(`let sheenColor = ${inputs.sheenColor ?? "vec3<f32>(0.0)"};`);
1954
+ lines.push(`let sheenRoughness = ${inputs.sheenRoughness ?? "0.0"};`);
1955
+ lines.push(`let subsurfaceThickness = ${inputs.subsurfaceThickness ?? "0.0"};`);
1956
+ lines.push(`let subsurfaceColor = ${inputs.subsurfaceColor ?? "vec3<f32>(0.0)"};`);
1957
+ lines.push(`let anisotropyVal = ${inputs.anisotropy ?? "0.0"};`);
1958
+ lines.push(`let anisotropyRotation = ${inputs.anisotropyRotation ?? "0.0"};`);
1959
+ lines.push(`let clearcoatVal = ${inputs.clearcoat ?? "0.0"};`);
1960
+ lines.push(`let clearcoatRoughnessVal = ${inputs.clearcoatRoughness ?? "0.0"};`);
1961
+ lines.push(`let iridescenceVal = ${inputs.iridescence ?? "0.0"};`);
1962
+ lines.push(`let sparkleIntensity = ${inputs.sparkleIntensity ?? "0.0"};`);
1963
+ lines.push(`let sparkleDensity = ${inputs.sparkleDensity ?? "0.0"};`);
1964
+ lines.push(`let fluorescenceColor = ${inputs.fluorescenceColor ?? "vec3<f32>(0.0)"};`);
1965
+ lines.push(`let fluorescenceIntensity = ${inputs.fluorescenceIntensity ?? "0.0"};`);
1966
+ lines.push(`let blackbodyTemp = ${inputs.blackbodyTemp ?? "0.0"};`);
1967
+ lines.push(`let retroreflectionVal = ${inputs.retroreflection ?? "0.0"};`);
1968
+ } else if (node.type === "output_volume") {
1969
+ lines.push(`// Volumetric output \u2014 ray march`);
1970
+ lines.push(`let volDensity = ${inputs.density ?? "0.5"};`);
1971
+ lines.push(`let volColor = ${inputs.color ?? "vec3<f32>(1.0)"};`);
1972
+ lines.push(`let volEmission = ${inputs.emission ?? "vec3<f32>(0.0)"};`);
1973
+ lines.push(`let volScattering = ${inputs.scattering ?? "0.5"};`);
1974
+ lines.push(`let volAbsorption = ${inputs.absorption ?? "0.1"};`);
1975
+ lines.push(`let volSteps = ${inputs.steps ?? "64.0"};`);
1976
+ lines.push(`let volMaxDist = ${inputs.maxDistance ?? "10.0"};`);
1977
+ } else if (node.type === "output_unlit") {
1978
+ lines.push(`// Unlit output`);
1979
+ lines.push(`let finalColor = ${inputs.color};`);
1980
+ } else if (node.outputs.length > 0) {
1981
+ if (this.options.debug) {
1982
+ lines.push(`// ${node.name} (${node.id})`);
1983
+ }
1984
+ if (node.outputs.length === 1) {
1985
+ const varName = this.getOutputVariable(node.id, node.outputs[0].id);
1986
+ const wgslType = this.getWGSLType(node.outputs[0].type);
1987
+ lines.push(`let ${varName}: ${wgslType} = ${code};`);
1988
+ } else {
1989
+ this.generateMultiOutputCode(node, inputs, lines);
1990
+ }
1991
+ }
1992
+ }
1993
+ return lines.join("\n ");
1994
+ }
1995
+ /**
1996
+ * Generate code for nodes with multiple outputs
1997
+ */
1998
+ generateMultiOutputCode(node, inputs, lines) {
1999
+ const template = getNodeTemplate(node.type);
2000
+ if (!template) return;
2001
+ if (node.type.startsWith("vector_split_")) {
2002
+ const inputExpr = inputs.vector;
2003
+ for (const output of node.outputs) {
2004
+ const varName = this.getOutputVariable(node.id, output.id);
2005
+ const wgslType = this.getWGSLType(output.type);
2006
+ lines.push(`let ${varName}: ${wgslType} = (${inputExpr}).${output.id};`);
2007
+ }
2008
+ return;
2009
+ }
2010
+ const baseCode = template.generateCode(node, inputs);
2011
+ const tempVar = this.generateVariable();
2012
+ lines.push(`let ${tempVar} = ${baseCode};`);
2013
+ for (const output of node.outputs) {
2014
+ const varName = this.getOutputVariable(node.id, output.id);
2015
+ lines.push(`let ${varName} = ${tempVar}.${output.id};`);
2016
+ }
2017
+ }
2018
+ /**
2019
+ * Generate vertex shader body
2020
+ */
2021
+ generateVertexBody(nodes) {
2022
+ const lines = [];
2023
+ const connections = this.graph.connections;
2024
+ const vertexOffsetNode = nodes.find((n) => n.type === "output_vertex_offset");
2025
+ if (vertexOffsetNode) {
2026
+ const template = getNodeTemplate(vertexOffsetNode.type);
2027
+ if (template) {
2028
+ const inputs = {};
2029
+ for (const port of vertexOffsetNode.inputs) {
2030
+ inputs[port.id] = this.getInputExpression(vertexOffsetNode, port.id, connections);
2031
+ }
2032
+ lines.push(`// Vertex offset`);
2033
+ lines.push(`let vertexOffset = ${inputs.offset};`);
2034
+ lines.push(`let worldPos = in.position + vertexOffset;`);
2035
+ }
2036
+ } else {
2037
+ lines.push(`let worldPos = in.position;`);
2038
+ }
2039
+ return lines.join("\n ");
2040
+ }
2041
+ /**
2042
+ * Generate complete vertex shader
2043
+ */
2044
+ generateVertexShader(body) {
2045
+ return `// Generated by HoloScript Shader Graph Compiler
2046
+ // Graph: ${this.graph.name}
2047
+
2048
+ struct VertexInput {
2049
+ @location(0) position: vec3<f32>,
2050
+ @location(1) normal: vec3<f32>,
2051
+ @location(2) uv: vec2<f32>,
2052
+ @location(3) tangent: vec4<f32>,
2053
+ };
2054
+
2055
+ struct VertexOutput {
2056
+ @builtin(position) clipPosition: vec4<f32>,
2057
+ @location(0) worldPosition: vec3<f32>,
2058
+ @location(1) worldNormal: vec3<f32>,
2059
+ @location(2) uv: vec2<f32>,
2060
+ };
2061
+
2062
+ struct CameraUniforms {
2063
+ viewProjection: mat4x4<f32>,
2064
+ view: mat4x4<f32>,
2065
+ projection: mat4x4<f32>,
2066
+ position: vec3<f32>,
2067
+ };
2068
+
2069
+ struct ModelUniforms {
2070
+ model: mat4x4<f32>,
2071
+ normalMatrix: mat3x3<f32>,
2072
+ };
2073
+
2074
+ @group(0) @binding(0) var<uniform> camera: CameraUniforms;
2075
+ @group(1) @binding(0) var<uniform> model: ModelUniforms;
2076
+
2077
+ @vertex
2078
+ fn main(in: VertexInput) -> VertexOutput {
2079
+ var out: VertexOutput;
2080
+
2081
+ ${body}
2082
+
2083
+ let worldPosition = (model.model * vec4<f32>(worldPos, 1.0)).xyz;
2084
+ let worldNormal = normalize(model.normalMatrix * in.normal);
2085
+
2086
+ out.worldPosition = worldPosition;
2087
+ out.worldNormal = worldNormal;
2088
+ out.uv = in.uv;
2089
+ out.clipPosition = camera.viewProjection * vec4<f32>(worldPosition, 1.0);
2090
+
2091
+ return out;
2092
+ }
2093
+ `;
2094
+ }
2095
+ /**
2096
+ * Generate complete fragment shader
2097
+ */
2098
+ generateFragmentShader(body) {
2099
+ const textureBindings = this.generateTextureBindings();
2100
+ const uniformBindings = this.generateUniformBindings();
2101
+ const helperFunctions = this.generateHelperFunctions();
2102
+ return `// Generated by HoloScript Shader Graph Compiler
2103
+ // Graph: ${this.graph.name}
2104
+
2105
+ struct FragmentInput {
2106
+ @location(0) worldPosition: vec3<f32>,
2107
+ @location(1) worldNormal: vec3<f32>,
2108
+ @location(2) uv: vec2<f32>,
2109
+ };
2110
+
2111
+ struct CameraUniforms {
2112
+ viewProjection: mat4x4<f32>,
2113
+ view: mat4x4<f32>,
2114
+ projection: mat4x4<f32>,
2115
+ position: vec3<f32>,
2116
+ };
2117
+
2118
+ struct SceneUniforms {
2119
+ ambientColor: vec3<f32>,
2120
+ time: f32,
2121
+ deltaTime: f32,
2122
+ };
2123
+
2124
+ struct LightData {
2125
+ position: vec3<f32>,
2126
+ color: vec3<f32>,
2127
+ intensity: f32,
2128
+ };
2129
+
2130
+ @group(0) @binding(0) var<uniform> camera: CameraUniforms;
2131
+ @group(0) @binding(1) var<uniform> scene: SceneUniforms;
2132
+ @group(0) @binding(2) var<uniform> light: LightData;
2133
+ ${textureBindings}
2134
+ ${uniformBindings}
2135
+
2136
+ const PI: f32 = 3.14159265359;
2137
+
2138
+ ${helperFunctions}
2139
+
2140
+ @fragment
2141
+ fn main(in: FragmentInput) -> @location(0) vec4<f32> {
2142
+ ${body}
2143
+
2144
+ // PBR lighting calculation
2145
+ let N = normalize(normal);
2146
+ let V = normalize(camera.position - in.worldPosition);
2147
+ let L = normalize(light.position - in.worldPosition);
2148
+ let H = normalize(V + L);
2149
+ let NdotV = max(dot(N, V), 0.0);
2150
+ let NdotL = max(dot(N, L), 0.0);
2151
+ let NdotH = max(dot(N, H), 0.0);
2152
+ let VdotH = max(dot(V, H), 0.0);
2153
+
2154
+ let F0 = mix(vec3<f32>(0.04), baseColor, metallic);
2155
+
2156
+ // Cook-Torrance BRDF
2157
+ let NDF = distributionGGX(N, H, roughness);
2158
+ let G = geometrySmith(N, V, L, roughness);
2159
+ let F = fresnelSchlick(VdotH, F0);
2160
+
2161
+ let numerator = NDF * G * F;
2162
+ let denominator = 4.0 * NdotV * NdotL + 0.0001;
2163
+ let specular = numerator / denominator;
2164
+
2165
+ let kS = F;
2166
+ let kD = (vec3<f32>(1.0) - kS) * (1.0 - metallic);
2167
+
2168
+ var Lo = (kD * baseColor / PI + specular) * light.color * light.intensity * NdotL;
2169
+
2170
+ // Sheen \u2014 soft fuzzy reflection layer (fabrics, velvet)
2171
+ if (sheenRoughness > 0.0 || length(sheenColor) > 0.0) {
2172
+ let sheenD = distributionGGX(N, H, sheenRoughness);
2173
+ let sheenTerm = sheenColor * sheenD * NdotL;
2174
+ Lo = Lo + sheenTerm * light.color * light.intensity;
2175
+ }
2176
+
2177
+ // Clearcoat \u2014 protective clear layer (car paint, varnish)
2178
+ if (clearcoatVal > 0.0) {
2179
+ let ccNDF = distributionGGX(N, H, clearcoatRoughnessVal);
2180
+ let ccG = geometrySmith(N, V, L, clearcoatRoughnessVal);
2181
+ let ccF = fresnelSchlick(VdotH, vec3<f32>(0.04));
2182
+ let ccSpec = (ccNDF * ccG * ccF) / (4.0 * NdotV * NdotL + 0.0001);
2183
+ Lo = Lo + ccSpec * clearcoatVal * light.color * light.intensity * NdotL;
2184
+ }
2185
+
2186
+ // Subsurface scattering approximation (skin, wax, leaves, jade)
2187
+ var sssContrib = vec3<f32>(0.0);
2188
+ if (subsurfaceThickness > 0.0) {
2189
+ let scatterVec = normalize(L + N * 0.5);
2190
+ let sssNdotL = max(dot(V, -scatterVec), 0.0);
2191
+ let sssFalloff = pow(sssNdotL, 3.0) * subsurfaceThickness;
2192
+ sssContrib = subsurfaceColor * sssFalloff * light.color * light.intensity;
2193
+ Lo = Lo + sssContrib;
2194
+ }
2195
+
2196
+ // Iridescence \u2014 thin-film interference (soap bubbles, oil slicks, beetles)
2197
+ if (iridescenceVal > 0.0) {
2198
+ let thinFilmPhase = VdotH * 6.28318;
2199
+ let iriColor = vec3<f32>(
2200
+ 0.5 + 0.5 * cos(thinFilmPhase),
2201
+ 0.5 + 0.5 * cos(thinFilmPhase + 2.094),
2202
+ 0.5 + 0.5 * cos(thinFilmPhase + 4.189)
2203
+ );
2204
+ Lo = mix(Lo, Lo * iriColor, iridescenceVal * 0.5);
2205
+ }
2206
+
2207
+ // Sparkle/glitter \u2014 stochastic micro-facet flashing
2208
+ if (sparkleIntensity > 0.0) {
2209
+ let sparkleUV = in.uv * sparkleDensity;
2210
+ let sparkleCell = floor(sparkleUV);
2211
+ let sparkleRand = simpleNoise(sparkleCell);
2212
+ let sparkleAngle = sparkleRand * 6.28318;
2213
+ let sparkleNormal = vec3<f32>(cos(sparkleAngle) * 0.3, sin(sparkleAngle) * 0.3, 0.9);
2214
+ let sparkleReflect = max(dot(reflect(-V, normalize(sparkleNormal)), L), 0.0);
2215
+ let sparkleFlash = pow(sparkleReflect, 64.0) * step(0.85, sparkleRand) * sparkleIntensity;
2216
+ Lo = Lo + vec3<f32>(sparkleFlash) * light.color * light.intensity;
2217
+ }
2218
+
2219
+ // Blackbody radiation \u2014 temperature-driven emission color (Planck approximation)
2220
+ if (blackbodyTemp > 0.0) {
2221
+ let bbColor = blackbodyColor(blackbodyTemp, 1.0);
2222
+ Lo = Lo + bbColor;
2223
+ }
2224
+
2225
+ // Fluorescence \u2014 absorb short \u03BB, re-emit longer \u03BB
2226
+ if (fluorescenceIntensity > 0.0) {
2227
+ let excitationMatch = dot(normalize(light.color), normalize(fluorescenceColor));
2228
+ let fluorEmit = fluorescenceColor * max(excitationMatch, 0.3) * fluorescenceIntensity;
2229
+ Lo = Lo + fluorEmit;
2230
+ }
2231
+
2232
+ // Retroreflection \u2014 light bounces back toward source
2233
+ if (retroreflectionVal > 0.0) {
2234
+ let retroDot = max(dot(V, L), 0.0);
2235
+ let retroTerm = pow(retroDot, 8.0) * retroreflectionVal;
2236
+ Lo = Lo + baseColor * retroTerm * light.color * light.intensity;
2237
+ }
2238
+
2239
+ var color = scene.ambientColor * baseColor * ao + Lo + emission;
2240
+
2241
+ // Volumetric contribution (if output_volume is connected)
2242
+ // The volDensity variable is set by output_volume node; if absent the default is 0
2243
+ // which makes the rayMarchVolume return transparent, so the surface pass dominates.
2244
+
2245
+ // Tone mapping (Reinhard)
2246
+ color = color / (color + vec3<f32>(1.0));
2247
+
2248
+ // Gamma correction
2249
+ color = pow(color, vec3<f32>(1.0 / 2.2));
2250
+
2251
+ return vec4<f32>(color, alpha);
2252
+ }
2253
+ `;
2254
+ }
2255
+ /**
2256
+ * Generate texture bindings
2257
+ */
2258
+ generateTextureBindings() {
2259
+ if (this.textures.length === 0) return "";
2260
+ const lines = [];
2261
+ for (const tex of this.textures) {
2262
+ const binding = tex.binding;
2263
+ lines.push(`@group(2) @binding(${binding}) var ${tex.name}: texture_2d<f32>;`);
2264
+ lines.push(`@group(2) @binding(${binding + 1}) var ${tex.name}Sampler: sampler;`);
2265
+ }
2266
+ return lines.join("\n");
2267
+ }
2268
+ /**
2269
+ * Generate uniform bindings
2270
+ */
2271
+ generateUniformBindings() {
2272
+ if (this.uniforms.length === 0) return "";
2273
+ const lines = ["struct MaterialUniforms {"];
2274
+ for (const uniform of this.uniforms) {
2275
+ const wgslType = this.getWGSLType(uniform.type);
2276
+ lines.push(` ${uniform.name}: ${wgslType},`);
2277
+ }
2278
+ lines.push("};");
2279
+ lines.push("@group(2) @binding(100) var<uniform> material: MaterialUniforms;");
2280
+ return lines.join("\n");
2281
+ }
2282
+ /**
2283
+ * Generate helper functions
2284
+ */
2285
+ generateHelperFunctions() {
2286
+ return `
2287
+ // PBR helper functions
2288
+ fn distributionGGX(N: vec3<f32>, H: vec3<f32>, roughness: f32) -> f32 {
2289
+ let a = roughness * roughness;
2290
+ let a2 = a * a;
2291
+ let NdotH = max(dot(N, H), 0.0);
2292
+ let NdotH2 = NdotH * NdotH;
2293
+
2294
+ let num = a2;
2295
+ let denom = (NdotH2 * (a2 - 1.0) + 1.0);
2296
+ let denom2 = PI * denom * denom;
2297
+
2298
+ return num / denom2;
2299
+ }
2300
+
2301
+ fn geometrySchlickGGX(NdotV: f32, roughness: f32) -> f32 {
2302
+ let r = roughness + 1.0;
2303
+ let k = (r * r) / 8.0;
2304
+
2305
+ let num = NdotV;
2306
+ let denom = NdotV * (1.0 - k) + k;
2307
+
2308
+ return num / denom;
2309
+ }
2310
+
2311
+ fn geometrySmith(N: vec3<f32>, V: vec3<f32>, L: vec3<f32>, roughness: f32) -> f32 {
2312
+ let NdotV = max(dot(N, V), 0.0);
2313
+ let NdotL = max(dot(N, L), 0.0);
2314
+ let ggx2 = geometrySchlickGGX(NdotV, roughness);
2315
+ let ggx1 = geometrySchlickGGX(NdotL, roughness);
2316
+
2317
+ return ggx1 * ggx2;
2318
+ }
2319
+
2320
+ fn fresnelSchlick(cosTheta: f32, F0: vec3<f32>) -> vec3<f32> {
2321
+ return F0 + (vec3<f32>(1.0) - F0) * pow(saturate(1.0 - cosTheta), 5.0);
2322
+ }
2323
+
2324
+ fn hueShift(color: vec3<f32>, shift: f32) -> vec3<f32> {
2325
+ let k = vec3<f32>(0.57735);
2326
+ let cosAngle = cos(shift);
2327
+ return color * cosAngle + cross(k, color) * sin(shift) + k * dot(k, color) * (1.0 - cosAngle);
2328
+ }
2329
+
2330
+ fn simpleNoise(uv: vec2<f32>) -> f32 {
2331
+ return fract(sin(dot(uv, vec2<f32>(12.9898, 78.233))) * 43758.5453);
2332
+ }
2333
+
2334
+ fn gradientNoise(uv: vec2<f32>) -> f32 {
2335
+ let i = floor(uv);
2336
+ let f = fract(uv);
2337
+ let u = f * f * (3.0 - 2.0 * f);
2338
+
2339
+ let n00 = simpleNoise(i);
2340
+ let n10 = simpleNoise(i + vec2<f32>(1.0, 0.0));
2341
+ let n01 = simpleNoise(i + vec2<f32>(0.0, 1.0));
2342
+ let n11 = simpleNoise(i + vec2<f32>(1.0, 1.0));
2343
+
2344
+ return mix(mix(n00, n10, u.x), mix(n01, n11, u.x), u.y);
2345
+ }
2346
+
2347
+ fn voronoi(uv: vec2<f32>) -> vec2<f32> {
2348
+ let n = floor(uv);
2349
+ let f = fract(uv);
2350
+
2351
+ var minDist = 1.0;
2352
+ var cellId = 0.0;
2353
+
2354
+ for (var j = -1; j <= 1; j++) {
2355
+ for (var i = -1; i <= 1; i++) {
2356
+ let neighbor = vec2<f32>(f32(i), f32(j));
2357
+ let point = simpleNoise(n + neighbor);
2358
+ let diff = neighbor + point - f;
2359
+ let dist = length(diff);
2360
+ if (dist < minDist) {
2361
+ minDist = dist;
2362
+ cellId = simpleNoise(n + neighbor);
2363
+ }
2364
+ }
2365
+ }
2366
+
2367
+ return vec2<f32>(cellId, minDist);
2368
+ }
2369
+
2370
+ // =========================================================================
2371
+ // Advanced Material Functions
2372
+ // =========================================================================
2373
+
2374
+ // Blackbody radiation \u2014 Planck approximation: temperature (Kelvin) \u2192 RGB color
2375
+ fn blackbodyColor(tempK: f32, intensity: f32) -> vec3<f32> {
2376
+ // CIE 1931 approximation for blackbody emission
2377
+ let t = clamp(tempK, 1000.0, 40000.0) / 100.0;
2378
+ var r: f32; var g: f32; var b: f32;
2379
+
2380
+ // Red channel
2381
+ if (t <= 66.0) {
2382
+ r = 1.0;
2383
+ } else {
2384
+ r = pow((t - 60.0) / 40.0, -0.1332);
2385
+ r = clamp(r, 0.0, 1.0);
2386
+ }
2387
+
2388
+ // Green channel
2389
+ if (t <= 66.0) {
2390
+ g = 0.39 * log(t) - 0.63;
2391
+ } else {
2392
+ g = 1.29 * pow((t - 60.0) / 40.0, -0.0755);
2393
+ }
2394
+ g = clamp(g, 0.0, 1.0);
2395
+
2396
+ // Blue channel
2397
+ if (t >= 66.0) {
2398
+ b = 1.0;
2399
+ } else if (t <= 19.0) {
2400
+ b = 0.0;
2401
+ } else {
2402
+ b = 0.543 * log(t - 10.0) - 1.19;
2403
+ b = clamp(b, 0.0, 1.0);
2404
+ }
2405
+
2406
+ return vec3<f32>(r, g, b) * intensity;
2407
+ }
2408
+
2409
+ // Sparkle / Glitter \u2014 stochastic micro-facet flash
2410
+ fn sparkleFlash(uv: vec2<f32>, density: f32, size: f32, intensity: f32, viewDir: vec3<f32>, normal: vec3<f32>) -> vec2<f32> {
2411
+ let cell = floor(uv * density);
2412
+ let rand = simpleNoise(cell);
2413
+ let threshold = 1.0 - size;
2414
+ let flash = step(threshold, rand) * intensity;
2415
+ return vec2<f32>(flash, rand);
2416
+ }
2417
+
2418
+ // Animated pattern \u2014 time-varying surface effects
2419
+ fn animatedPattern(uv: vec2<f32>, time: f32, patternType: f32, speed: f32, amplitude: f32, scale: f32) -> vec2<f32> {
2420
+ let t = time * speed;
2421
+ let suv = uv * scale;
2422
+ var value: f32 = 0.0;
2423
+ var uvOffset = vec2<f32>(0.0);
2424
+
2425
+ let pType = i32(patternType);
2426
+
2427
+ // 0: ripple \u2014 concentric rings from center
2428
+ if (pType == 0) {
2429
+ let dist = length(suv - vec2<f32>(0.5));
2430
+ value = sin(dist * 20.0 - t * 4.0) * 0.5 + 0.5;
2431
+ }
2432
+ // 1: flicker \u2014 random temporal noise
2433
+ else if (pType == 1) {
2434
+ value = simpleNoise(vec2<f32>(t * 3.0, 0.0));
2435
+ }
2436
+ // 2: pulse \u2014 breathing sine wave
2437
+ else if (pType == 2) {
2438
+ value = sin(t * 2.0) * 0.5 + 0.5;
2439
+ }
2440
+ // 3: flow \u2014 directional scroll
2441
+ else if (pType == 3) {
2442
+ uvOffset = vec2<f32>(t * 0.1, 0.0);
2443
+ value = simpleNoise(suv + uvOffset);
2444
+ }
2445
+ // 4: breathe \u2014 slow amplitude modulation
2446
+ else if (pType == 4) {
2447
+ value = sin(t * 0.5) * 0.5 + 0.5;
2448
+ value = value * value; // ease-in
2449
+ }
2450
+ // 5: scroll \u2014 vertical scroll
2451
+ else if (pType == 5) {
2452
+ uvOffset = vec2<f32>(0.0, t * 0.1);
2453
+ value = gradientNoise(suv + uvOffset);
2454
+ }
2455
+ // 6: wave \u2014 sinusoidal displacement
2456
+ else if (pType == 6) {
2457
+ value = sin(suv.x * 10.0 + t * 3.0) * sin(suv.y * 10.0 + t * 2.0) * 0.5 + 0.5;
2458
+ }
2459
+ // 7: noise \u2014 animated Perlin
2460
+ else {
2461
+ value = gradientNoise(suv + vec2<f32>(t * 0.2));
2462
+ }
2463
+
2464
+ return vec2<f32>(value * amplitude, 0.0);
2465
+ }
2466
+
2467
+ // Weathering \u2014 procedural surface degradation
2468
+ fn weatheringSurface(uv: vec2<f32>, progress: f32, weatherType: f32, seed: f32, baseColor: vec3<f32>) -> vec3<f32> {
2469
+ let noise1 = gradientNoise(uv * 8.0 + vec2<f32>(seed));
2470
+ let noise2 = simpleNoise(uv * 16.0 + vec2<f32>(seed * 2.0));
2471
+ let mask = smoothstep(1.0 - progress, 1.0 - progress + 0.2, noise1);
2472
+
2473
+ let wType = i32(weatherType);
2474
+ var weatherColor = vec3<f32>(0.5);
2475
+
2476
+ // 0: rust
2477
+ if (wType == 0) { weatherColor = vec3<f32>(0.55, 0.22, 0.08); }
2478
+ // 1: moss
2479
+ else if (wType == 1) { weatherColor = vec3<f32>(0.29, 0.49, 0.25); }
2480
+ // 2: crack \u2014 darken along cracks
2481
+ else if (wType == 2) { weatherColor = baseColor * 0.3; }
2482
+ // 3: peel \u2014 reveal underlayer
2483
+ else if (wType == 3) { weatherColor = vec3<f32>(0.85, 0.82, 0.78); }
2484
+ // 4: patina
2485
+ else if (wType == 4) { weatherColor = vec3<f32>(0.29, 0.55, 0.49); }
2486
+ // 5: frost
2487
+ else if (wType == 5) { weatherColor = vec3<f32>(0.9, 0.95, 1.0); }
2488
+ // 6: burn
2489
+ else if (wType == 6) { weatherColor = vec3<f32>(0.1, 0.08, 0.05); }
2490
+ // 7: erosion
2491
+ else if (wType == 7) { weatherColor = baseColor * (0.5 + noise2 * 0.3); }
2492
+ // 8: stain
2493
+ else if (wType == 8) { weatherColor = mix(baseColor, vec3<f32>(0.4, 0.35, 0.25), 0.6); }
2494
+ // 9: dust
2495
+ else { weatherColor = mix(baseColor, vec3<f32>(0.75, 0.7, 0.65), 0.4); }
2496
+
2497
+ return mix(baseColor, weatherColor, mask);
2498
+ }
2499
+
2500
+ // Dual-layer material blend
2501
+ fn dualLayerBlend(
2502
+ baseCol: vec3<f32>, baseRough: f32, baseMetal: f32,
2503
+ topCol: vec3<f32>, topRough: f32, topMetal: f32,
2504
+ blend: f32, mode: f32, normal: vec3<f32>, uv: vec2<f32>
2505
+ ) -> vec3<f32> {
2506
+ var factor = blend;
2507
+
2508
+ let modeInt = i32(mode);
2509
+ // 0: linear
2510
+ // 1: height-based (top appears on upward-facing surfaces)
2511
+ if (modeInt == 1) {
2512
+ factor = factor * max(normal.y, 0.0);
2513
+ }
2514
+ // 2: noise-based
2515
+ else if (modeInt == 2) {
2516
+ let n = gradientNoise(uv * 8.0);
2517
+ factor = smoothstep(0.5 - blend * 0.5, 0.5 + blend * 0.5, n);
2518
+ }
2519
+ // 3: fresnel-based (top appears at grazing angles)
2520
+ else if (modeInt == 3) {
2521
+ factor = factor; // fresnel computed in main lighting \u2014 blend is direct here
2522
+ }
2523
+
2524
+ return mix(baseCol, topCol, factor);
2525
+ }
2526
+
2527
+ // Fluorescence \u2014 absorb excitation wavelengths, emit shifted color
2528
+ fn fluorescenceEmit(lightColor: vec3<f32>, excitationColor: vec3<f32>, emissionColor: vec3<f32>, intensity: f32) -> vec3<f32> {
2529
+ let match = dot(normalize(lightColor), normalize(excitationColor));
2530
+ return emissionColor * max(match, 0.0) * intensity;
2531
+ }
2532
+
2533
+ // Retroreflection \u2014 light reflects back toward its source
2534
+ fn retroReflect(viewDir: vec3<f32>, lightDir: vec3<f32>, normal: vec3<f32>, intensity: f32) -> f32 {
2535
+ let retroDot = max(dot(viewDir, lightDir), 0.0);
2536
+ return pow(retroDot, 8.0) * intensity;
2537
+ }
2538
+
2539
+ // =========================================================================
2540
+ // Volumetric Material Functions \u2014 Ray Marching, 3D Noise, Scattering
2541
+ // =========================================================================
2542
+
2543
+ // 3D noise \u2014 hash-based, for volumetric density
2544
+ fn noise3D(p: vec3<f32>) -> f32 {
2545
+ let i = floor(p);
2546
+ let f = fract(p);
2547
+ let u = f * f * (3.0 - 2.0 * f);
2548
+
2549
+ let n000 = simpleNoise(i.xy + vec2<f32>(0.0, i.z * 37.0));
2550
+ let n100 = simpleNoise(i.xy + vec2<f32>(1.0, i.z * 37.0));
2551
+ let n010 = simpleNoise(i.xy + vec2<f32>(0.0, (i.z + 1.0) * 37.0 + 17.0));
2552
+ let n110 = simpleNoise(i.xy + vec2<f32>(1.0, (i.z + 1.0) * 37.0 + 17.0));
2553
+ let n001 = simpleNoise(i.xy + vec2<f32>(i.z * 37.0 + 59.0, 0.0));
2554
+ let n101 = simpleNoise(i.xy + vec2<f32>(i.z * 37.0 + 60.0, 0.0));
2555
+ let n011 = simpleNoise(i.xy + vec2<f32>((i.z + 1.0) * 37.0 + 59.0, 17.0));
2556
+ let n111 = simpleNoise(i.xy + vec2<f32>((i.z + 1.0) * 37.0 + 60.0, 17.0));
2557
+
2558
+ let x0 = mix(mix(n000, n100, u.x), mix(n010, n110, u.x), u.y);
2559
+ let x1 = mix(mix(n001, n101, u.x), mix(n011, n111, u.x), u.y);
2560
+ return mix(x0, x1, u.z);
2561
+ }
2562
+
2563
+ // Fractal Brownian Motion in 3D \u2014 layered noise octaves
2564
+ fn fbmNoise3D(p: vec3<f32>, octaves: i32, lacunarity: f32, gain: f32) -> f32 {
2565
+ var value = 0.0;
2566
+ var amplitude = 1.0;
2567
+ var frequency = 1.0;
2568
+ var pos = p;
2569
+
2570
+ for (var i = 0; i < octaves; i++) {
2571
+ value = value + amplitude * noise3D(pos * frequency);
2572
+ amplitude = amplitude * gain;
2573
+ frequency = frequency * lacunarity;
2574
+ }
2575
+
2576
+ return value;
2577
+ }
2578
+
2579
+ // Curl noise 3D \u2014 divergence-free noise for smoke/fire turbulence
2580
+ fn curlNoise3D(p: vec3<f32>) -> vec3<f32> {
2581
+ let eps = 0.01;
2582
+ let dx = vec3<f32>(eps, 0.0, 0.0);
2583
+ let dy = vec3<f32>(0.0, eps, 0.0);
2584
+ let dz = vec3<f32>(0.0, 0.0, eps);
2585
+
2586
+ let dFzdy = noise3D(p + dy) - noise3D(p - dy);
2587
+ let dFydz = noise3D(p + dz) - noise3D(p - dz);
2588
+ let dFxdz = noise3D(p + dz + dx) - noise3D(p - dz + dx);
2589
+ let dFzdx = noise3D(p + dx) - noise3D(p - dx);
2590
+ let dFydx = noise3D(p + dx + dy) - noise3D(p - dx + dy);
2591
+ let dFxdy = noise3D(p + dy + dx) - noise3D(p - dy + dx);
2592
+
2593
+ return vec3<f32>(
2594
+ dFzdy - dFydz,
2595
+ dFxdz - dFzdx,
2596
+ dFydx - dFxdy
2597
+ ) / (2.0 * eps);
2598
+ }
2599
+
2600
+ // Volume density at a 3D position with FBM noise
2601
+ fn volumeDensity(pos: vec3<f32>, baseDensity: f32, noiseScale: f32, noiseOctaves: f32, time: f32) -> f32 {
2602
+ let animatedPos = pos + vec3<f32>(0.0, time * 0.1, 0.0);
2603
+ let n = fbmNoise3D(animatedPos * noiseScale, i32(noiseOctaves), 2.0, 0.5);
2604
+ return clamp(baseDensity * n, 0.0, 1.0);
2605
+ }
2606
+
2607
+ // Height fog density \u2014 exponential falloff with height
2608
+ fn heightFogDensity(pos: vec3<f32>, groundLevel: f32, density: f32, falloff: f32) -> f32 {
2609
+ let height = pos.y - groundLevel;
2610
+ return density * exp(-max(height, 0.0) * falloff);
2611
+ }
2612
+
2613
+ // Fire density \u2014 buoyant rising, turbulent, temperature-driven
2614
+ fn fireDensity(pos: vec3<f32>, time: f32, turbulence: f32, riseSpeed: f32, scale: f32) -> vec2<f32> {
2615
+ // Rising motion
2616
+ let risingPos = pos - vec3<f32>(0.0, time * riseSpeed, 0.0);
2617
+ // Turbulence via curl noise
2618
+ let curl = curlNoise3D(risingPos * scale * 0.5) * turbulence;
2619
+ let distortedPos = risingPos + curl;
2620
+ // Base density from FBM
2621
+ let d = fbmNoise3D(distortedPos * scale, 4, 2.0, 0.5);
2622
+ // Height falloff \u2014 fire thins out as it rises
2623
+ let heightFade = exp(-max(pos.y, 0.0) * 0.5);
2624
+ let density = clamp(d * heightFade, 0.0, 1.0);
2625
+ // Temperature \u2014 hotter at base, cooler at top
2626
+ let temperature = clamp(density * (1.0 - pos.y * 0.3) * 3000.0 + 800.0, 800.0, 3000.0);
2627
+ return vec2<f32>(density, temperature);
2628
+ }
2629
+
2630
+ // Henyey-Greenstein phase function \u2014 anisotropic light scattering
2631
+ fn henyeyGreenstein(cosTheta: f32, g: f32) -> f32 {
2632
+ let g2 = g * g;
2633
+ let denom = 1.0 + g2 - 2.0 * g * cosTheta;
2634
+ return (1.0 - g2) / (4.0 * PI * pow(denom, 1.5));
2635
+ }
2636
+
2637
+ // Beer-Lambert transmittance \u2014 light attenuation through absorbing medium
2638
+ fn beerLambert(density: f32, stepLen: f32, absorption: f32) -> f32 {
2639
+ return exp(-density * absorption * stepLen);
2640
+ }
2641
+
2642
+ // Volume scattering \u2014 transmittance + in-scattering at a sample point
2643
+ fn volumeScatter(density: f32, scattering: f32, absorption: f32, stepLen: f32, lightDir: vec3<f32>, viewDir: vec3<f32>, phaseAniso: f32) -> vec2<f32> {
2644
+ let cosTheta = dot(normalize(lightDir), normalize(viewDir));
2645
+ let phase = henyeyGreenstein(cosTheta, phaseAniso);
2646
+ let transmittance = beerLambert(density, stepLen, absorption + scattering);
2647
+ let inScatter = density * scattering * phase * stepLen;
2648
+ return vec2<f32>(transmittance, inScatter);
2649
+ }
2650
+
2651
+ // Volume emission \u2014 self-luminous contribution (fire, neon, aurora)
2652
+ fn volumeEmission(density: f32, emissionColor: vec3<f32>, intensity: f32, temperature: f32) -> vec3<f32> {
2653
+ var emCol = emissionColor * intensity * density;
2654
+ // If temperature > 0, blend with blackbody color
2655
+ if (temperature > 0.0) {
2656
+ let bbCol = blackbodyColor(temperature, 1.0);
2657
+ emCol = emCol * bbCol;
2658
+ }
2659
+ return emCol;
2660
+ }
2661
+
2662
+ // Ray-box intersection \u2014 returns (tNear, tFar) for axis-aligned bounding box
2663
+ fn rayBoxIntersect(rayOrigin: vec3<f32>, rayDir: vec3<f32>, boxMin: vec3<f32>, boxMax: vec3<f32>) -> vec2<f32> {
2664
+ let invDir = 1.0 / rayDir;
2665
+ let t1 = (boxMin - rayOrigin) * invDir;
2666
+ let t2 = (boxMax - rayOrigin) * invDir;
2667
+ let tMin = min(t1, t2);
2668
+ let tMax = max(t1, t2);
2669
+ let tNear = max(max(tMin.x, tMin.y), tMin.z);
2670
+ let tFar = min(min(tMax.x, tMax.y), tMax.z);
2671
+ return vec2<f32>(max(tNear, 0.0), tFar);
2672
+ }
2673
+
2674
+ // Main ray march loop \u2014 integrates density, color, emission along a ray
2675
+ fn rayMarchVolume(
2676
+ rayOrigin: vec3<f32>, rayDir: vec3<f32>,
2677
+ volDensity: f32, volColor: vec3<f32>, volEmission: vec3<f32>,
2678
+ scattering: f32, absorption: f32,
2679
+ steps: f32, maxDist: f32
2680
+ ) -> vec4<f32> {
2681
+ // Intersect unit cube [-1,1]^3
2682
+ let hit = rayBoxIntersect(rayOrigin, rayDir, vec3<f32>(-1.0), vec3<f32>(1.0));
2683
+ if (hit.x > hit.y) {
2684
+ return vec4<f32>(0.0); // Miss
2685
+ }
2686
+
2687
+ let stepCount = i32(steps);
2688
+ let tStart = hit.x;
2689
+ let tEnd = min(hit.y, maxDist);
2690
+ let stepLen = (tEnd - tStart) / f32(stepCount);
2691
+
2692
+ var accColor = vec3<f32>(0.0);
2693
+ var accTransmittance = 1.0;
2694
+
2695
+ for (var i = 0; i < stepCount; i++) {
2696
+ if (accTransmittance < 0.01) { break; } // Early exit
2697
+
2698
+ let t = tStart + (f32(i) + 0.5) * stepLen;
2699
+ let samplePos = rayOrigin + rayDir * t;
2700
+
2701
+ // Sample density with noise
2702
+ let d = volumeDensity(samplePos, volDensity, 4.0, 4.0, scene.time);
2703
+ if (d < 0.001) { continue; } // Skip empty space
2704
+
2705
+ // Transmittance for this step (Beer-Lambert)
2706
+ let stepTransmittance = beerLambert(d, stepLen, absorption + scattering);
2707
+
2708
+ // In-scattering from light
2709
+ let lightDir = normalize(light.position - samplePos);
2710
+ let cosTheta = dot(lightDir, rayDir);
2711
+ let phase = henyeyGreenstein(cosTheta, 0.3);
2712
+ let inScatter = d * scattering * phase * stepLen * light.intensity;
2713
+
2714
+ // Emission
2715
+ let emissionContrib = volEmission * d * stepLen;
2716
+
2717
+ // Accumulate
2718
+ let sampleColor = volColor * inScatter * light.color + emissionContrib;
2719
+ accColor = accColor + sampleColor * accTransmittance;
2720
+ accTransmittance = accTransmittance * stepTransmittance;
2721
+ }
2722
+
2723
+ return vec4<f32>(accColor, 1.0 - accTransmittance);
2724
+ }
2725
+
2726
+ // =========================================================================
2727
+ // Caustics \u2014 underwater refracted light patterns
2728
+ // =========================================================================
2729
+
2730
+ // Voronoi-based caustic pattern (dual-layer for temporal stability)
2731
+ fn causticVoronoi(p: vec2<f32>) -> f32 {
2732
+ let i = floor(p);
2733
+ let f = fract(p);
2734
+ var minDist = 1.0;
2735
+ for (var y = -1; y <= 1; y++) {
2736
+ for (var x = -1; x <= 1; x++) {
2737
+ let neighbor = vec2<f32>(f32(x), f32(y));
2738
+ let cellHash = fract(sin(dot(i + neighbor, vec2<f32>(127.1, 311.7))) * 43758.5453);
2739
+ let cellHash2 = fract(sin(dot(i + neighbor, vec2<f32>(269.5, 183.3))) * 43758.5453);
2740
+ let point = neighbor + vec2<f32>(cellHash, cellHash2) - f;
2741
+ let dist = dot(point, point);
2742
+ minDist = min(minDist, dist);
2743
+ }
2744
+ }
2745
+ return sqrt(minDist);
2746
+ }
2747
+
2748
+ fn causticPattern(
2749
+ pos: vec3<f32>, time: f32, scale: f32, speed: f32,
2750
+ intensity: f32, color: vec3<f32>
2751
+ ) -> vec3<f32> {
2752
+ // Dual-layer caustics for interference pattern
2753
+ let uv1 = pos.xz * scale + vec2<f32>(time * speed * 0.3, time * speed * 0.7);
2754
+ let uv2 = pos.xz * scale * 1.3 + vec2<f32>(-time * speed * 0.5, time * speed * 0.4);
2755
+ let c1 = causticVoronoi(uv1);
2756
+ let c2 = causticVoronoi(uv2);
2757
+ // Combine layers \u2014 bright where both layers converge
2758
+ let caustic = pow(1.0 - c1, 3.0) * pow(1.0 - c2, 3.0) * intensity;
2759
+ return color * caustic;
2760
+ }
2761
+
2762
+ // Chromatic refractive caustics: separate R/G/B with different IoR for prismatic effect
2763
+ fn causticChromatic(
2764
+ pos: vec3<f32>, time: f32, scale: f32, speed: f32,
2765
+ intensity: f32, dispersion: f32
2766
+ ) -> vec3<f32> {
2767
+ let baseUV = pos.xz * scale;
2768
+ let t = time * speed;
2769
+ // Offset UV per channel based on dispersion
2770
+ let uvR = baseUV + vec2<f32>(t * 0.3 + dispersion, t * 0.7);
2771
+ let uvG = baseUV + vec2<f32>(t * 0.3, t * 0.7);
2772
+ let uvB = baseUV + vec2<f32>(t * 0.3 - dispersion, t * 0.7);
2773
+ // Dual-layer per channel
2774
+ let r = pow(1.0 - causticVoronoi(uvR), 3.0) * pow(1.0 - causticVoronoi(uvR * 1.3 + vec2<f32>(-t * 0.5, t * 0.4)), 3.0);
2775
+ let g = pow(1.0 - causticVoronoi(uvG), 3.0) * pow(1.0 - causticVoronoi(uvG * 1.3 + vec2<f32>(-t * 0.5, t * 0.4)), 3.0);
2776
+ let b = pow(1.0 - causticVoronoi(uvB), 3.0) * pow(1.0 - causticVoronoi(uvB * 1.3 + vec2<f32>(-t * 0.5, t * 0.4)), 3.0);
2777
+ return vec3<f32>(r, g, b) * intensity;
2778
+ }
2779
+
2780
+ // Turbulence-driven underwater foam patches
2781
+ fn underwaterFoam(
2782
+ pos: vec3<f32>, time: f32, scale: f32, threshold: f32
2783
+ ) -> f32 {
2784
+ let uv = pos.xz * scale;
2785
+ // Multi-octave turbulence
2786
+ var turb = 0.0;
2787
+ turb += abs(simpleNoise(uv * 2.0 + vec2<f32>(time * 0.1, 0.0)) - 0.5) * 2.0;
2788
+ turb += abs(simpleNoise(uv * 4.0 + vec2<f32>(0.0, time * 0.15)) - 0.5) * 1.0;
2789
+ turb += abs(simpleNoise(uv * 8.0 + vec2<f32>(time * 0.2, time * 0.1)) - 0.5) * 0.5;
2790
+ turb /= 3.5;
2791
+ return smoothstep(threshold, threshold + 0.15, turb);
2792
+ }
2793
+
2794
+ // =========================================================================
2795
+ // Displacement Mapping \u2014 vertex offset along normal
2796
+ // =========================================================================
2797
+
2798
+ fn displacementMap(
2799
+ position: vec3<f32>, normal: vec3<f32>,
2800
+ height: f32, strength: f32, midLevel: f32
2801
+ ) -> vec3<f32> {
2802
+ let offset = (height - midLevel) * strength;
2803
+ return position + normal * offset;
2804
+ }
2805
+
2806
+ // Reconstruct normal from height field via finite differences
2807
+ fn displacementNormal(
2808
+ uv: vec2<f32>, texelSize: f32, strength: f32
2809
+ ) -> vec3<f32> {
2810
+ let hL = simpleNoise(uv - vec2<f32>(texelSize, 0.0)) * strength;
2811
+ let hR = simpleNoise(uv + vec2<f32>(texelSize, 0.0)) * strength;
2812
+ let hD = simpleNoise(uv - vec2<f32>(0.0, texelSize)) * strength;
2813
+ let hU = simpleNoise(uv + vec2<f32>(0.0, texelSize)) * strength;
2814
+ return normalize(vec3<f32>(hL - hR, 2.0 * texelSize, hD - hU));
2815
+ }
2816
+
2817
+ // Anisotropic displacement along normal with tangent-space weighting
2818
+ fn anisotropicDisplacement(
2819
+ position: vec3<f32>, normal: vec3<f32>, tangent: vec3<f32>,
2820
+ height: f32, strength: f32, anisotropy: f32, midLevel: f32
2821
+ ) -> vec3<f32> {
2822
+ let offset = (height - midLevel) * strength;
2823
+ let bitangent = cross(normal, tangent);
2824
+ // Blend between isotropic (normal) and anisotropic (tangent-weighted) displacement
2825
+ let isoDisp = normal * offset;
2826
+ let anisoDisp = (tangent * anisotropy + normal * (1.0 - abs(anisotropy))) * offset;
2827
+ return position + mix(isoDisp, anisoDisp, abs(anisotropy));
2828
+ }
2829
+
2830
+ // =========================================================================
2831
+ // Parallax Occlusion Mapping \u2014 steep parallax with occlusion
2832
+ // =========================================================================
2833
+
2834
+ fn parallaxOcclusionMap(
2835
+ uv: vec2<f32>, viewDir: vec3<f32>,
2836
+ heightScale: f32, numLayers: f32
2837
+ ) -> vec2<f32> {
2838
+ let layerCount = i32(numLayers);
2839
+ let layerDepth = 1.0 / numLayers;
2840
+ var currentLayerDepth = 0.0;
2841
+ let deltaUV = viewDir.xy / viewDir.z * heightScale / numLayers;
2842
+
2843
+ var currentUV = uv;
2844
+ var currentDepthMapValue = simpleNoise(currentUV); // Use noise as height
2845
+
2846
+ // Step through layers from front to back
2847
+ for (var i = 0; i < layerCount; i++) {
2848
+ if (currentLayerDepth >= currentDepthMapValue) { break; }
2849
+ currentUV -= deltaUV;
2850
+ currentDepthMapValue = simpleNoise(currentUV);
2851
+ currentLayerDepth += layerDepth;
2852
+ }
2853
+
2854
+ // Binary refinement for smoother result
2855
+ let prevUV = currentUV + deltaUV;
2856
+ let afterDepth = currentDepthMapValue - currentLayerDepth;
2857
+ let beforeDepth = simpleNoise(prevUV) - currentLayerDepth + layerDepth;
2858
+ let weight = afterDepth / (afterDepth - beforeDepth);
2859
+ return mix(currentUV, prevUV, weight);
2860
+ }
2861
+
2862
+ // =========================================================================
2863
+ // Screen-Space Reflections (SSR) \u2014 ray march in screen space
2864
+ // =========================================================================
2865
+
2866
+ fn screenSpaceReflect(
2867
+ uv: vec2<f32>, normal: vec3<f32>, viewDir: vec3<f32>,
2868
+ roughness: f32, maxSteps: f32, stepSize: f32
2869
+ ) -> vec3<f32> {
2870
+ // Reflect view direction around surface normal
2871
+ let reflectDir = reflect(viewDir, normal);
2872
+
2873
+ // Convert reflection to screen-space step direction
2874
+ let screenStep = reflectDir.xy * stepSize;
2875
+
2876
+ var hitColor = vec3<f32>(0.0);
2877
+ var hitMask = 0.0;
2878
+ var sampleUV = uv;
2879
+ var currentDepth = 0.0;
2880
+ let steps = i32(maxSteps);
2881
+
2882
+ for (var i = 0; i < steps; i++) {
2883
+ sampleUV += screenStep;
2884
+
2885
+ // Bounds check
2886
+ if (sampleUV.x < 0.0 || sampleUV.x > 1.0 || sampleUV.y < 0.0 || sampleUV.y > 1.0) {
2887
+ break;
2888
+ }
2889
+
2890
+ // In a real implementation, we'd sample the depth buffer here
2891
+ // For the shader graph, we approximate with noise-based depth
2892
+ let sampleDepth = simpleNoise(sampleUV * 10.0);
2893
+ currentDepth += stepSize;
2894
+
2895
+ if (currentDepth > sampleDepth) {
2896
+ // Hit \u2014 sample color at hit point
2897
+ hitColor = vec3<f32>(sampleUV, 0.5); // Placeholder: real impl samples color buffer
2898
+ hitMask = 1.0;
2899
+
2900
+ // Fade with roughness (rough surfaces \u2192 less clear reflection)
2901
+ hitMask *= 1.0 - roughness;
2902
+
2903
+ // Fade at screen edges
2904
+ let edgeFade = 1.0 - pow(max(abs(sampleUV.x - 0.5), abs(sampleUV.y - 0.5)) * 2.0, 4.0);
2905
+ hitMask *= max(edgeFade, 0.0);
2906
+ break;
2907
+ }
2908
+ }
2909
+
2910
+ return hitColor * hitMask;
2911
+ }
2912
+
2913
+ // =========================================================================
2914
+ // Screen-Space Global Illumination (SSGI) \u2014 approximate indirect lighting
2915
+ // =========================================================================
2916
+
2917
+ fn screenSpaceGI(
2918
+ uv: vec2<f32>, normal: vec3<f32>,
2919
+ radius: f32, samples: f32, bounceIntensity: f32
2920
+ ) -> vec3<f32> {
2921
+ var indirect = vec3<f32>(0.0);
2922
+ let sampleCount = i32(samples);
2923
+ let goldenAngle = 2.399963; // Golden angle in radians
2924
+
2925
+ for (var i = 0; i < sampleCount; i++) {
2926
+ let fi = f32(i);
2927
+ // Fibonacci spiral sampling pattern
2928
+ let r = sqrt(fi / samples) * radius;
2929
+ let theta = fi * goldenAngle;
2930
+ let offset = vec2<f32>(cos(theta), sin(theta)) * r;
2931
+ let sampleUV = uv + offset * 0.01; // Scale to UV space
2932
+
2933
+ // Sample nearby color (in real impl, from color buffer)
2934
+ let sampleColor = vec3<f32>(
2935
+ simpleNoise(sampleUV * 20.0),
2936
+ simpleNoise(sampleUV * 20.0 + vec2<f32>(37.0, 0.0)),
2937
+ simpleNoise(sampleUV * 20.0 + vec2<f32>(0.0, 53.0))
2938
+ );
2939
+
2940
+ // Weight by cosine of angle between sample direction and normal
2941
+ let sampleDir = normalize(vec3<f32>(offset, 0.1));
2942
+ let cosWeight = max(dot(sampleDir, normal), 0.0);
2943
+
2944
+ indirect += sampleColor * cosWeight;
2945
+ }
2946
+
2947
+ indirect /= samples;
2948
+ return indirect * bounceIntensity;
2949
+ }
2950
+
2951
+ // =========================================================================
2952
+ // Water Surface \u2014 combined waves, normals, foam, caustics
2953
+ // =========================================================================
2954
+
2955
+ struct WaterSurfaceResult {
2956
+ displacement: vec3<f32>,
2957
+ normal: vec3<f32>,
2958
+ foam: f32,
2959
+ caustic: f32,
2960
+ }
2961
+
2962
+ fn gerstnerWave(pos: vec2<f32>, time: f32, dir: vec2<f32>, steepness: f32, wavelength: f32) -> vec3<f32> {
2963
+ let k = 6.28318 / wavelength;
2964
+ let c = sqrt(9.81 / k);
2965
+ let d = normalize(dir);
2966
+ let f = k * (dot(d, pos) - c * time);
2967
+ let a = steepness / k;
2968
+ return vec3<f32>(d.x * a * cos(f), a * sin(f), d.y * a * cos(f));
2969
+ }
2970
+
2971
+ fn waterSurface(
2972
+ pos: vec3<f32>, time: f32,
2973
+ waveScale: f32, waveSpeed: f32,
2974
+ waterDepth: f32, foamThreshold: f32
2975
+ ) -> WaterSurfaceResult {
2976
+ let scaledTime = time * waveSpeed;
2977
+ let p = pos.xz * waveScale;
2978
+
2979
+ // Sum of Gerstner waves at different frequencies and directions
2980
+ var displacement = vec3<f32>(0.0);
2981
+ displacement += gerstnerWave(p, scaledTime, vec2<f32>(1.0, 0.3), 0.25, 4.0);
2982
+ displacement += gerstnerWave(p, scaledTime, vec2<f32>(-0.5, 0.8), 0.15, 2.5);
2983
+ displacement += gerstnerWave(p, scaledTime, vec2<f32>(0.3, -0.7), 0.1, 1.5);
2984
+ displacement += gerstnerWave(p, scaledTime, vec2<f32>(-0.8, -0.3), 0.08, 0.8);
2985
+
2986
+ // Compute normal via finite differences on all 4 wave layers
2987
+ let eps = 0.01;
2988
+ let px = p + vec2<f32>(eps, 0.0);
2989
+ let pz = p + vec2<f32>(0.0, eps);
2990
+ var dx = vec3<f32>(0.0);
2991
+ dx += gerstnerWave(px, scaledTime, vec2<f32>(1.0, 0.3), 0.25, 4.0);
2992
+ dx += gerstnerWave(px, scaledTime, vec2<f32>(-0.5, 0.8), 0.15, 2.5);
2993
+ dx += gerstnerWave(px, scaledTime, vec2<f32>(0.3, -0.7), 0.1, 1.5);
2994
+ dx += gerstnerWave(px, scaledTime, vec2<f32>(-0.8, -0.3), 0.08, 0.8);
2995
+ var dz = vec3<f32>(0.0);
2996
+ dz += gerstnerWave(pz, scaledTime, vec2<f32>(1.0, 0.3), 0.25, 4.0);
2997
+ dz += gerstnerWave(pz, scaledTime, vec2<f32>(-0.5, 0.8), 0.15, 2.5);
2998
+ dz += gerstnerWave(pz, scaledTime, vec2<f32>(0.3, -0.7), 0.1, 1.5);
2999
+ dz += gerstnerWave(pz, scaledTime, vec2<f32>(-0.8, -0.3), 0.08, 0.8);
3000
+ let waterNormal = normalize(vec3<f32>(
3001
+ -(dx.y - displacement.y) / eps,
3002
+ 1.0,
3003
+ -(dz.y - displacement.y) / eps
3004
+ ));
3005
+
3006
+ // Foam: where wave crests converge (Jacobian < threshold)
3007
+ let jacobian = 1.0 - abs(displacement.x) * waveScale;
3008
+ let foam = smoothstep(foamThreshold, foamThreshold + 0.1, 1.0 - jacobian);
3009
+
3010
+ // Caustics (project through water depth)
3011
+ let causticsUV = p + displacement.xz * 0.5;
3012
+ let c1 = causticVoronoi(causticsUV * 3.0 + vec2<f32>(scaledTime * 0.3, 0.0));
3013
+ let caustic = pow(1.0 - c1, 4.0) * exp(-waterDepth * 0.3);
3014
+
3015
+ return WaterSurfaceResult(displacement, waterNormal, foam, caustic);
3016
+ }
3017
+ `;
3018
+ }
3019
+ };
3020
+ function compileShaderGraph(graph, options) {
3021
+ const compiler = new ShaderGraphCompiler(graph, options);
3022
+ return compiler.compile();
3023
+ }
3024
+ export {
3025
+ ADVANCED_MATERIAL_NODES,
3026
+ ALL_NODE_TEMPLATES,
3027
+ COLOR_NODES,
3028
+ INPUT_NODES,
3029
+ MATH_NODES,
3030
+ OUTPUT_NODES,
3031
+ SCREEN_SPACE_NODES,
3032
+ ShaderGraph,
3033
+ ShaderGraphCompiler,
3034
+ TEXTURE_NODES,
3035
+ TRIG_NODES,
3036
+ TYPE_SIZES,
3037
+ UTILITY_NODES,
3038
+ VECTOR_NODES,
3039
+ VOLUMETRIC_NODES,
3040
+ areTypesCompatible,
3041
+ compileShaderGraph,
3042
+ getNodeTemplate,
3043
+ getTypeConversion
3044
+ };