Skip to main content

proof_engine/shader_graph/
nodes.rs

1//! Shader graph node definitions: 40+ node types with input/output sockets,
2//! GLSL snippet generation, and default parameter values.
3
4use std::collections::HashMap;
5use std::fmt;
6
7// ---------------------------------------------------------------------------
8// Core ID types
9// ---------------------------------------------------------------------------
10
11/// Unique identifier for a node within a graph.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13pub struct NodeId(pub u64);
14
15/// Unique identifier for a socket (node_id, socket_index, direction).
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
17pub struct SocketId {
18    pub node_id: NodeId,
19    pub index: usize,
20    pub direction: SocketDirection,
21}
22
23/// Direction of a socket — input or output.
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
25pub enum SocketDirection {
26    Input,
27    Output,
28}
29
30/// Data type flowing through a socket.
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32pub enum DataType {
33    Float,
34    Vec2,
35    Vec3,
36    Vec4,
37    Mat3,
38    Mat4,
39    Sampler2D,
40    Bool,
41    Int,
42}
43
44impl fmt::Display for DataType {
45    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
46        match self {
47            DataType::Float => write!(f, "float"),
48            DataType::Vec2 => write!(f, "vec2"),
49            DataType::Vec3 => write!(f, "vec3"),
50            DataType::Vec4 => write!(f, "vec4"),
51            DataType::Mat3 => write!(f, "mat3"),
52            DataType::Mat4 => write!(f, "mat4"),
53            DataType::Sampler2D => write!(f, "sampler2D"),
54            DataType::Bool => write!(f, "bool"),
55            DataType::Int => write!(f, "int"),
56        }
57    }
58}
59
60// ---------------------------------------------------------------------------
61// Socket definition
62// ---------------------------------------------------------------------------
63
64/// A socket on a node — either an input or an output.
65#[derive(Debug, Clone)]
66pub struct Socket {
67    pub name: String,
68    pub data_type: DataType,
69    pub direction: SocketDirection,
70    pub default_value: Option<ParamValue>,
71}
72
73impl Socket {
74    pub fn input(name: &str, dt: DataType) -> Self {
75        Self { name: name.to_string(), data_type: dt, direction: SocketDirection::Input, default_value: None }
76    }
77
78    pub fn input_default(name: &str, dt: DataType, val: ParamValue) -> Self {
79        Self { name: name.to_string(), data_type: dt, direction: SocketDirection::Input, default_value: Some(val) }
80    }
81
82    pub fn output(name: &str, dt: DataType) -> Self {
83        Self { name: name.to_string(), data_type: dt, direction: SocketDirection::Output, default_value: None }
84    }
85}
86
87// ---------------------------------------------------------------------------
88// Parameter values
89// ---------------------------------------------------------------------------
90
91/// Concrete parameter value that can be stored in a socket default or node property.
92#[derive(Debug, Clone, PartialEq)]
93pub enum ParamValue {
94    Float(f32),
95    Vec2([f32; 2]),
96    Vec3([f32; 3]),
97    Vec4([f32; 4]),
98    Int(i32),
99    Bool(bool),
100    String(String),
101}
102
103impl ParamValue {
104    pub fn as_float(&self) -> Option<f32> {
105        match self { ParamValue::Float(v) => Some(*v), _ => None }
106    }
107    pub fn as_vec2(&self) -> Option<[f32; 2]> {
108        match self { ParamValue::Vec2(v) => Some(*v), _ => None }
109    }
110    pub fn as_vec3(&self) -> Option<[f32; 3]> {
111        match self { ParamValue::Vec3(v) => Some(*v), _ => None }
112    }
113    pub fn as_vec4(&self) -> Option<[f32; 4]> {
114        match self { ParamValue::Vec4(v) => Some(*v), _ => None }
115    }
116    pub fn as_int(&self) -> Option<i32> {
117        match self { ParamValue::Int(v) => Some(*v), _ => None }
118    }
119    pub fn as_bool(&self) -> Option<bool> {
120        match self { ParamValue::Bool(v) => Some(*v), _ => None }
121    }
122    pub fn as_string(&self) -> Option<&str> {
123        match self { ParamValue::String(v) => Some(v.as_str()), _ => None }
124    }
125
126    /// Produce a GLSL literal for this value.
127    pub fn to_glsl(&self) -> String {
128        match self {
129            ParamValue::Float(v) => format_float(*v),
130            ParamValue::Vec2(v) => format!("vec2({}, {})", format_float(v[0]), format_float(v[1])),
131            ParamValue::Vec3(v) => format!("vec3({}, {}, {})", format_float(v[0]), format_float(v[1]), format_float(v[2])),
132            ParamValue::Vec4(v) => format!("vec4({}, {}, {}, {})", format_float(v[0]), format_float(v[1]), format_float(v[2]), format_float(v[3])),
133            ParamValue::Int(v) => format!("{}", v),
134            ParamValue::Bool(v) => if *v { "true".to_string() } else { "false".to_string() },
135            ParamValue::String(v) => v.clone(),
136        }
137    }
138
139    /// Return the DataType that corresponds to this value.
140    pub fn data_type(&self) -> DataType {
141        match self {
142            ParamValue::Float(_) => DataType::Float,
143            ParamValue::Vec2(_) => DataType::Vec2,
144            ParamValue::Vec3(_) => DataType::Vec3,
145            ParamValue::Vec4(_) => DataType::Vec4,
146            ParamValue::Int(_) => DataType::Int,
147            ParamValue::Bool(_) => DataType::Bool,
148            ParamValue::String(_) => DataType::Float, // strings are typically uniform names
149        }
150    }
151}
152
153fn format_float(v: f32) -> String {
154    if v == v.floor() && v.abs() < 1e9 {
155        format!("{:.1}", v)
156    } else {
157        format!("{}", v)
158    }
159}
160
161// ---------------------------------------------------------------------------
162// Node types — 40+ variants
163// ---------------------------------------------------------------------------
164
165/// All supported shader node types.
166#[derive(Debug, Clone, PartialEq)]
167pub enum NodeType {
168    // ── Sources ──────────────────────────────────────────────
169    Color,
170    Texture,
171    VertexPosition,
172    VertexNormal,
173    Time,
174    CameraPos,
175    GameStateVar,
176
177    // ── Transforms ──────────────────────────────────────────
178    Translate,
179    Rotate,
180    Scale,
181    WorldToLocal,
182    LocalToWorld,
183
184    // ── Math ────────────────────────────────────────────────
185    Add,
186    Sub,
187    Mul,
188    Div,
189    Dot,
190    Cross,
191    Normalize,
192    Length,
193    Abs,
194    Floor,
195    Ceil,
196    Fract,
197    Mod,
198    Pow,
199    Sqrt,
200    Sin,
201    Cos,
202    Tan,
203    Atan2,
204    Lerp,
205    Clamp,
206    Smoothstep,
207    Remap,
208    Step,
209
210    // ── Effects ─────────────────────────────────────────────
211    Fresnel,
212    Dissolve,
213    Distortion,
214    Blur,
215    Sharpen,
216    EdgeDetect,
217    Outline,
218    Bloom,
219    ChromaticAberration,
220
221    // ── Color ───────────────────────────────────────────────
222    HSVToRGB,
223    RGBToHSV,
224    Contrast,
225    Saturation,
226    Hue,
227    Invert,
228    Posterize,
229    GradientMap,
230
231    // ── Noise ───────────────────────────────────────────────
232    Perlin,
233    Simplex,
234    Voronoi,
235    FBM,
236    Turbulence,
237
238    // ── Output ──────────────────────────────────────────────
239    MainColor,
240    EmissionBuffer,
241    BloomBuffer,
242    NormalOutput,
243}
244
245impl NodeType {
246    /// Human-readable display name.
247    pub fn display_name(&self) -> &'static str {
248        match self {
249            NodeType::Color => "Color",
250            NodeType::Texture => "Texture Sample",
251            NodeType::VertexPosition => "Vertex Position",
252            NodeType::VertexNormal => "Vertex Normal",
253            NodeType::Time => "Time",
254            NodeType::CameraPos => "Camera Position",
255            NodeType::GameStateVar => "Game State Variable",
256            NodeType::Translate => "Translate",
257            NodeType::Rotate => "Rotate",
258            NodeType::Scale => "Scale",
259            NodeType::WorldToLocal => "World To Local",
260            NodeType::LocalToWorld => "Local To World",
261            NodeType::Add => "Add",
262            NodeType::Sub => "Subtract",
263            NodeType::Mul => "Multiply",
264            NodeType::Div => "Divide",
265            NodeType::Dot => "Dot Product",
266            NodeType::Cross => "Cross Product",
267            NodeType::Normalize => "Normalize",
268            NodeType::Length => "Length",
269            NodeType::Abs => "Absolute",
270            NodeType::Floor => "Floor",
271            NodeType::Ceil => "Ceil",
272            NodeType::Fract => "Fract",
273            NodeType::Mod => "Modulo",
274            NodeType::Pow => "Power",
275            NodeType::Sqrt => "Square Root",
276            NodeType::Sin => "Sine",
277            NodeType::Cos => "Cosine",
278            NodeType::Tan => "Tangent",
279            NodeType::Atan2 => "Atan2",
280            NodeType::Lerp => "Lerp",
281            NodeType::Clamp => "Clamp",
282            NodeType::Smoothstep => "Smoothstep",
283            NodeType::Remap => "Remap",
284            NodeType::Step => "Step",
285            NodeType::Fresnel => "Fresnel",
286            NodeType::Dissolve => "Dissolve",
287            NodeType::Distortion => "Distortion",
288            NodeType::Blur => "Blur",
289            NodeType::Sharpen => "Sharpen",
290            NodeType::EdgeDetect => "Edge Detect",
291            NodeType::Outline => "Outline",
292            NodeType::Bloom => "Bloom",
293            NodeType::ChromaticAberration => "Chromatic Aberration",
294            NodeType::HSVToRGB => "HSV to RGB",
295            NodeType::RGBToHSV => "RGB to HSV",
296            NodeType::Contrast => "Contrast",
297            NodeType::Saturation => "Saturation",
298            NodeType::Hue => "Hue Shift",
299            NodeType::Invert => "Invert",
300            NodeType::Posterize => "Posterize",
301            NodeType::GradientMap => "Gradient Map",
302            NodeType::Perlin => "Perlin Noise",
303            NodeType::Simplex => "Simplex Noise",
304            NodeType::Voronoi => "Voronoi",
305            NodeType::FBM => "FBM",
306            NodeType::Turbulence => "Turbulence",
307            NodeType::MainColor => "Main Color Output",
308            NodeType::EmissionBuffer => "Emission Buffer",
309            NodeType::BloomBuffer => "Bloom Buffer",
310            NodeType::NormalOutput => "Normal Output",
311        }
312    }
313
314    /// Category string for grouping in a UI palette.
315    pub fn category(&self) -> &'static str {
316        match self {
317            NodeType::Color | NodeType::Texture | NodeType::VertexPosition
318            | NodeType::VertexNormal | NodeType::Time | NodeType::CameraPos
319            | NodeType::GameStateVar => "Source",
320
321            NodeType::Translate | NodeType::Rotate | NodeType::Scale
322            | NodeType::WorldToLocal | NodeType::LocalToWorld => "Transform",
323
324            NodeType::Add | NodeType::Sub | NodeType::Mul | NodeType::Div
325            | NodeType::Dot | NodeType::Cross | NodeType::Normalize | NodeType::Length
326            | NodeType::Abs | NodeType::Floor | NodeType::Ceil | NodeType::Fract
327            | NodeType::Mod | NodeType::Pow | NodeType::Sqrt | NodeType::Sin
328            | NodeType::Cos | NodeType::Tan | NodeType::Atan2 | NodeType::Lerp
329            | NodeType::Clamp | NodeType::Smoothstep | NodeType::Remap
330            | NodeType::Step => "Math",
331
332            NodeType::Fresnel | NodeType::Dissolve | NodeType::Distortion
333            | NodeType::Blur | NodeType::Sharpen | NodeType::EdgeDetect
334            | NodeType::Outline | NodeType::Bloom
335            | NodeType::ChromaticAberration => "Effect",
336
337            NodeType::HSVToRGB | NodeType::RGBToHSV | NodeType::Contrast
338            | NodeType::Saturation | NodeType::Hue | NodeType::Invert
339            | NodeType::Posterize | NodeType::GradientMap => "Color",
340
341            NodeType::Perlin | NodeType::Simplex | NodeType::Voronoi
342            | NodeType::FBM | NodeType::Turbulence => "Noise",
343
344            NodeType::MainColor | NodeType::EmissionBuffer | NodeType::BloomBuffer
345            | NodeType::NormalOutput => "Output",
346        }
347    }
348
349    /// Whether this node type is an output sink (terminal node).
350    pub fn is_output(&self) -> bool {
351        matches!(self, NodeType::MainColor | NodeType::EmissionBuffer | NodeType::BloomBuffer | NodeType::NormalOutput)
352    }
353
354    /// Whether this node is a pure math operation (eligible for constant folding).
355    pub fn is_pure_math(&self) -> bool {
356        matches!(self,
357            NodeType::Add | NodeType::Sub | NodeType::Mul | NodeType::Div
358            | NodeType::Dot | NodeType::Cross | NodeType::Normalize | NodeType::Length
359            | NodeType::Abs | NodeType::Floor | NodeType::Ceil | NodeType::Fract
360            | NodeType::Mod | NodeType::Pow | NodeType::Sqrt | NodeType::Sin
361            | NodeType::Cos | NodeType::Tan | NodeType::Atan2 | NodeType::Lerp
362            | NodeType::Clamp | NodeType::Smoothstep | NodeType::Remap | NodeType::Step
363            | NodeType::HSVToRGB | NodeType::RGBToHSV | NodeType::Contrast
364            | NodeType::Saturation | NodeType::Hue | NodeType::Invert
365            | NodeType::Posterize
366        )
367    }
368
369    /// Whether this node is a source that requires no inputs from other nodes.
370    pub fn is_source(&self) -> bool {
371        matches!(self,
372            NodeType::Color | NodeType::Texture | NodeType::VertexPosition
373            | NodeType::VertexNormal | NodeType::Time | NodeType::CameraPos
374            | NodeType::GameStateVar
375        )
376    }
377
378    /// Estimated GPU instruction cost of this node (for budgeting).
379    pub fn instruction_cost(&self) -> u32 {
380        match self {
381            // Sources are essentially free (reads)
382            NodeType::Color => 0,
383            NodeType::VertexPosition | NodeType::VertexNormal | NodeType::CameraPos => 1,
384            NodeType::Time => 0,
385            NodeType::GameStateVar => 1,
386            NodeType::Texture => 4,
387
388            // Transforms
389            NodeType::Translate | NodeType::Scale => 3,
390            NodeType::Rotate => 8,
391            NodeType::WorldToLocal | NodeType::LocalToWorld => 16,
392
393            // Simple math
394            NodeType::Add | NodeType::Sub => 1,
395            NodeType::Mul => 1,
396            NodeType::Div => 2,
397            NodeType::Dot => 3,
398            NodeType::Cross => 6,
399            NodeType::Normalize => 4,
400            NodeType::Length => 3,
401            NodeType::Abs | NodeType::Floor | NodeType::Ceil | NodeType::Fract => 1,
402            NodeType::Mod => 2,
403            NodeType::Pow => 4,
404            NodeType::Sqrt => 2,
405            NodeType::Sin | NodeType::Cos | NodeType::Tan => 4,
406            NodeType::Atan2 => 6,
407            NodeType::Lerp => 3,
408            NodeType::Clamp => 2,
409            NodeType::Smoothstep => 5,
410            NodeType::Remap => 6,
411            NodeType::Step => 1,
412
413            // Effects
414            NodeType::Fresnel => 8,
415            NodeType::Dissolve => 12,
416            NodeType::Distortion => 10,
417            NodeType::Blur => 32,
418            NodeType::Sharpen => 20,
419            NodeType::EdgeDetect => 24,
420            NodeType::Outline => 16,
421            NodeType::Bloom => 28,
422            NodeType::ChromaticAberration => 18,
423
424            // Color ops
425            NodeType::HSVToRGB | NodeType::RGBToHSV => 10,
426            NodeType::Contrast | NodeType::Saturation | NodeType::Hue => 6,
427            NodeType::Invert => 1,
428            NodeType::Posterize => 4,
429            NodeType::GradientMap => 8,
430
431            // Noise
432            NodeType::Perlin => 20,
433            NodeType::Simplex => 24,
434            NodeType::Voronoi => 30,
435            NodeType::FBM => 60,
436            NodeType::Turbulence => 50,
437
438            // Outputs are essentially writes
439            NodeType::MainColor | NodeType::EmissionBuffer
440            | NodeType::BloomBuffer | NodeType::NormalOutput => 1,
441        }
442    }
443
444    /// Build the default input sockets for this node type.
445    pub fn default_inputs(&self) -> Vec<Socket> {
446        match self {
447            // ── Sources ─────────────────────────────────────
448            NodeType::Color => vec![
449                Socket::input_default("color", DataType::Vec4, ParamValue::Vec4([1.0, 1.0, 1.0, 1.0])),
450            ],
451            NodeType::Texture => vec![
452                Socket::input("uv", DataType::Vec2),
453                Socket::input_default("sampler", DataType::Sampler2D, ParamValue::Int(0)),
454            ],
455            NodeType::VertexPosition => vec![],
456            NodeType::VertexNormal => vec![],
457            NodeType::Time => vec![
458                Socket::input_default("speed", DataType::Float, ParamValue::Float(1.0)),
459            ],
460            NodeType::CameraPos => vec![],
461            NodeType::GameStateVar => vec![
462                Socket::input_default("var_name", DataType::Float, ParamValue::String("game_var_0".to_string())),
463            ],
464
465            // ── Transforms ──────────────────────────────────
466            NodeType::Translate => vec![
467                Socket::input("position", DataType::Vec3),
468                Socket::input_default("offset", DataType::Vec3, ParamValue::Vec3([0.0, 0.0, 0.0])),
469            ],
470            NodeType::Rotate => vec![
471                Socket::input("position", DataType::Vec3),
472                Socket::input_default("axis", DataType::Vec3, ParamValue::Vec3([0.0, 1.0, 0.0])),
473                Socket::input_default("angle", DataType::Float, ParamValue::Float(0.0)),
474            ],
475            NodeType::Scale => vec![
476                Socket::input("position", DataType::Vec3),
477                Socket::input_default("factor", DataType::Vec3, ParamValue::Vec3([1.0, 1.0, 1.0])),
478            ],
479            NodeType::WorldToLocal => vec![
480                Socket::input("position", DataType::Vec3),
481                Socket::input("matrix", DataType::Mat4),
482            ],
483            NodeType::LocalToWorld => vec![
484                Socket::input("position", DataType::Vec3),
485                Socket::input("matrix", DataType::Mat4),
486            ],
487
488            // ── Math (binary) ───────────────────────────────
489            NodeType::Add => vec![
490                Socket::input_default("a", DataType::Float, ParamValue::Float(0.0)),
491                Socket::input_default("b", DataType::Float, ParamValue::Float(0.0)),
492            ],
493            NodeType::Sub => vec![
494                Socket::input_default("a", DataType::Float, ParamValue::Float(0.0)),
495                Socket::input_default("b", DataType::Float, ParamValue::Float(0.0)),
496            ],
497            NodeType::Mul => vec![
498                Socket::input_default("a", DataType::Float, ParamValue::Float(1.0)),
499                Socket::input_default("b", DataType::Float, ParamValue::Float(1.0)),
500            ],
501            NodeType::Div => vec![
502                Socket::input_default("a", DataType::Float, ParamValue::Float(1.0)),
503                Socket::input_default("b", DataType::Float, ParamValue::Float(1.0)),
504            ],
505            NodeType::Dot => vec![
506                Socket::input("a", DataType::Vec3),
507                Socket::input("b", DataType::Vec3),
508            ],
509            NodeType::Cross => vec![
510                Socket::input("a", DataType::Vec3),
511                Socket::input("b", DataType::Vec3),
512            ],
513            NodeType::Normalize => vec![
514                Socket::input("v", DataType::Vec3),
515            ],
516            NodeType::Length => vec![
517                Socket::input("v", DataType::Vec3),
518            ],
519            NodeType::Abs => vec![
520                Socket::input_default("x", DataType::Float, ParamValue::Float(0.0)),
521            ],
522            NodeType::Floor => vec![
523                Socket::input_default("x", DataType::Float, ParamValue::Float(0.0)),
524            ],
525            NodeType::Ceil => vec![
526                Socket::input_default("x", DataType::Float, ParamValue::Float(0.0)),
527            ],
528            NodeType::Fract => vec![
529                Socket::input_default("x", DataType::Float, ParamValue::Float(0.0)),
530            ],
531            NodeType::Mod => vec![
532                Socket::input_default("x", DataType::Float, ParamValue::Float(0.0)),
533                Socket::input_default("y", DataType::Float, ParamValue::Float(1.0)),
534            ],
535            NodeType::Pow => vec![
536                Socket::input_default("base", DataType::Float, ParamValue::Float(1.0)),
537                Socket::input_default("exp", DataType::Float, ParamValue::Float(1.0)),
538            ],
539            NodeType::Sqrt => vec![
540                Socket::input_default("x", DataType::Float, ParamValue::Float(1.0)),
541            ],
542            NodeType::Sin => vec![
543                Socket::input_default("x", DataType::Float, ParamValue::Float(0.0)),
544            ],
545            NodeType::Cos => vec![
546                Socket::input_default("x", DataType::Float, ParamValue::Float(0.0)),
547            ],
548            NodeType::Tan => vec![
549                Socket::input_default("x", DataType::Float, ParamValue::Float(0.0)),
550            ],
551            NodeType::Atan2 => vec![
552                Socket::input_default("y", DataType::Float, ParamValue::Float(0.0)),
553                Socket::input_default("x", DataType::Float, ParamValue::Float(1.0)),
554            ],
555            NodeType::Lerp => vec![
556                Socket::input_default("a", DataType::Float, ParamValue::Float(0.0)),
557                Socket::input_default("b", DataType::Float, ParamValue::Float(1.0)),
558                Socket::input_default("t", DataType::Float, ParamValue::Float(0.5)),
559            ],
560            NodeType::Clamp => vec![
561                Socket::input_default("x", DataType::Float, ParamValue::Float(0.0)),
562                Socket::input_default("min_val", DataType::Float, ParamValue::Float(0.0)),
563                Socket::input_default("max_val", DataType::Float, ParamValue::Float(1.0)),
564            ],
565            NodeType::Smoothstep => vec![
566                Socket::input_default("edge0", DataType::Float, ParamValue::Float(0.0)),
567                Socket::input_default("edge1", DataType::Float, ParamValue::Float(1.0)),
568                Socket::input_default("x", DataType::Float, ParamValue::Float(0.5)),
569            ],
570            NodeType::Remap => vec![
571                Socket::input_default("x", DataType::Float, ParamValue::Float(0.5)),
572                Socket::input_default("in_min", DataType::Float, ParamValue::Float(0.0)),
573                Socket::input_default("in_max", DataType::Float, ParamValue::Float(1.0)),
574                Socket::input_default("out_min", DataType::Float, ParamValue::Float(0.0)),
575                Socket::input_default("out_max", DataType::Float, ParamValue::Float(1.0)),
576            ],
577            NodeType::Step => vec![
578                Socket::input_default("edge", DataType::Float, ParamValue::Float(0.5)),
579                Socket::input_default("x", DataType::Float, ParamValue::Float(0.0)),
580            ],
581
582            // ── Effects ─────────────────────────────────────
583            NodeType::Fresnel => vec![
584                Socket::input("normal", DataType::Vec3),
585                Socket::input("view_dir", DataType::Vec3),
586                Socket::input_default("power", DataType::Float, ParamValue::Float(2.0)),
587                Socket::input_default("bias", DataType::Float, ParamValue::Float(0.0)),
588            ],
589            NodeType::Dissolve => vec![
590                Socket::input("color", DataType::Vec4),
591                Socket::input_default("noise", DataType::Float, ParamValue::Float(0.5)),
592                Socket::input_default("threshold", DataType::Float, ParamValue::Float(0.5)),
593                Socket::input_default("edge_width", DataType::Float, ParamValue::Float(0.05)),
594                Socket::input_default("edge_color", DataType::Vec4, ParamValue::Vec4([1.0, 0.5, 0.0, 1.0])),
595            ],
596            NodeType::Distortion => vec![
597                Socket::input("uv", DataType::Vec2),
598                Socket::input_default("strength", DataType::Float, ParamValue::Float(0.1)),
599                Socket::input_default("direction", DataType::Vec2, ParamValue::Vec2([1.0, 0.0])),
600                Socket::input_default("noise", DataType::Float, ParamValue::Float(0.0)),
601            ],
602            NodeType::Blur => vec![
603                Socket::input("uv", DataType::Vec2),
604                Socket::input_default("radius", DataType::Float, ParamValue::Float(2.0)),
605                Socket::input_default("samples", DataType::Int, ParamValue::Int(8)),
606                Socket::input("sampler", DataType::Sampler2D),
607            ],
608            NodeType::Sharpen => vec![
609                Socket::input("uv", DataType::Vec2),
610                Socket::input_default("strength", DataType::Float, ParamValue::Float(1.0)),
611                Socket::input("sampler", DataType::Sampler2D),
612            ],
613            NodeType::EdgeDetect => vec![
614                Socket::input("uv", DataType::Vec2),
615                Socket::input_default("threshold", DataType::Float, ParamValue::Float(0.1)),
616                Socket::input("sampler", DataType::Sampler2D),
617            ],
618            NodeType::Outline => vec![
619                Socket::input("color", DataType::Vec4),
620                Socket::input("depth", DataType::Float),
621                Socket::input("normal", DataType::Vec3),
622                Socket::input_default("width", DataType::Float, ParamValue::Float(1.0)),
623                Socket::input_default("outline_color", DataType::Vec4, ParamValue::Vec4([0.0, 0.0, 0.0, 1.0])),
624            ],
625            NodeType::Bloom => vec![
626                Socket::input("color", DataType::Vec4),
627                Socket::input_default("threshold", DataType::Float, ParamValue::Float(0.8)),
628                Socket::input_default("intensity", DataType::Float, ParamValue::Float(1.5)),
629                Socket::input_default("radius", DataType::Float, ParamValue::Float(4.0)),
630            ],
631            NodeType::ChromaticAberration => vec![
632                Socket::input("uv", DataType::Vec2),
633                Socket::input_default("offset", DataType::Float, ParamValue::Float(0.005)),
634                Socket::input("sampler", DataType::Sampler2D),
635            ],
636
637            // ── Color ───────────────────────────────────────
638            NodeType::HSVToRGB => vec![
639                Socket::input_default("h", DataType::Float, ParamValue::Float(0.0)),
640                Socket::input_default("s", DataType::Float, ParamValue::Float(1.0)),
641                Socket::input_default("v", DataType::Float, ParamValue::Float(1.0)),
642            ],
643            NodeType::RGBToHSV => vec![
644                Socket::input("rgb", DataType::Vec3),
645            ],
646            NodeType::Contrast => vec![
647                Socket::input("color", DataType::Vec3),
648                Socket::input_default("amount", DataType::Float, ParamValue::Float(1.0)),
649            ],
650            NodeType::Saturation => vec![
651                Socket::input("color", DataType::Vec3),
652                Socket::input_default("amount", DataType::Float, ParamValue::Float(1.0)),
653            ],
654            NodeType::Hue => vec![
655                Socket::input("color", DataType::Vec3),
656                Socket::input_default("shift", DataType::Float, ParamValue::Float(0.0)),
657            ],
658            NodeType::Invert => vec![
659                Socket::input("color", DataType::Vec3),
660            ],
661            NodeType::Posterize => vec![
662                Socket::input("color", DataType::Vec3),
663                Socket::input_default("levels", DataType::Float, ParamValue::Float(4.0)),
664            ],
665            NodeType::GradientMap => vec![
666                Socket::input_default("t", DataType::Float, ParamValue::Float(0.5)),
667                Socket::input_default("color_a", DataType::Vec3, ParamValue::Vec3([0.0, 0.0, 0.0])),
668                Socket::input_default("color_b", DataType::Vec3, ParamValue::Vec3([1.0, 1.0, 1.0])),
669            ],
670
671            // ── Noise ───────────────────────────────────────
672            NodeType::Perlin => vec![
673                Socket::input("position", DataType::Vec3),
674                Socket::input_default("scale", DataType::Float, ParamValue::Float(1.0)),
675                Socket::input_default("seed", DataType::Float, ParamValue::Float(0.0)),
676            ],
677            NodeType::Simplex => vec![
678                Socket::input("position", DataType::Vec3),
679                Socket::input_default("scale", DataType::Float, ParamValue::Float(1.0)),
680                Socket::input_default("seed", DataType::Float, ParamValue::Float(0.0)),
681            ],
682            NodeType::Voronoi => vec![
683                Socket::input("position", DataType::Vec3),
684                Socket::input_default("scale", DataType::Float, ParamValue::Float(1.0)),
685                Socket::input_default("jitter", DataType::Float, ParamValue::Float(1.0)),
686            ],
687            NodeType::FBM => vec![
688                Socket::input("position", DataType::Vec3),
689                Socket::input_default("scale", DataType::Float, ParamValue::Float(1.0)),
690                Socket::input_default("octaves", DataType::Int, ParamValue::Int(4)),
691                Socket::input_default("lacunarity", DataType::Float, ParamValue::Float(2.0)),
692                Socket::input_default("gain", DataType::Float, ParamValue::Float(0.5)),
693            ],
694            NodeType::Turbulence => vec![
695                Socket::input("position", DataType::Vec3),
696                Socket::input_default("scale", DataType::Float, ParamValue::Float(1.0)),
697                Socket::input_default("octaves", DataType::Int, ParamValue::Int(4)),
698                Socket::input_default("lacunarity", DataType::Float, ParamValue::Float(2.0)),
699                Socket::input_default("gain", DataType::Float, ParamValue::Float(0.5)),
700            ],
701
702            // ── Outputs ─────────────────────────────────────
703            NodeType::MainColor => vec![
704                Socket::input("color", DataType::Vec4),
705            ],
706            NodeType::EmissionBuffer => vec![
707                Socket::input("emission", DataType::Vec4),
708            ],
709            NodeType::BloomBuffer => vec![
710                Socket::input("bloom", DataType::Vec4),
711            ],
712            NodeType::NormalOutput => vec![
713                Socket::input("normal", DataType::Vec3),
714            ],
715        }
716    }
717
718    /// Build the default output sockets for this node type.
719    pub fn default_outputs(&self) -> Vec<Socket> {
720        match self {
721            // Sources
722            NodeType::Color => vec![Socket::output("color", DataType::Vec4)],
723            NodeType::Texture => vec![
724                Socket::output("color", DataType::Vec4),
725                Socket::output("r", DataType::Float),
726                Socket::output("g", DataType::Float),
727                Socket::output("b", DataType::Float),
728                Socket::output("a", DataType::Float),
729            ],
730            NodeType::VertexPosition => vec![Socket::output("position", DataType::Vec3)],
731            NodeType::VertexNormal => vec![Socket::output("normal", DataType::Vec3)],
732            NodeType::Time => vec![
733                Socket::output("time", DataType::Float),
734                Socket::output("sin_time", DataType::Float),
735                Socket::output("cos_time", DataType::Float),
736                Socket::output("fract_time", DataType::Float),
737            ],
738            NodeType::CameraPos => vec![Socket::output("position", DataType::Vec3)],
739            NodeType::GameStateVar => vec![Socket::output("value", DataType::Float)],
740
741            // Transforms
742            NodeType::Translate | NodeType::Rotate | NodeType::Scale
743            | NodeType::WorldToLocal | NodeType::LocalToWorld => {
744                vec![Socket::output("result", DataType::Vec3)]
745            }
746
747            // Binary math
748            NodeType::Add | NodeType::Sub | NodeType::Mul | NodeType::Div => {
749                vec![Socket::output("result", DataType::Float)]
750            }
751            NodeType::Dot => vec![Socket::output("result", DataType::Float)],
752            NodeType::Cross => vec![Socket::output("result", DataType::Vec3)],
753            NodeType::Normalize => vec![Socket::output("result", DataType::Vec3)],
754            NodeType::Length => vec![Socket::output("result", DataType::Float)],
755            NodeType::Abs | NodeType::Floor | NodeType::Ceil | NodeType::Fract
756            | NodeType::Mod | NodeType::Pow | NodeType::Sqrt => {
757                vec![Socket::output("result", DataType::Float)]
758            }
759            NodeType::Sin | NodeType::Cos | NodeType::Tan | NodeType::Atan2 => {
760                vec![Socket::output("result", DataType::Float)]
761            }
762            NodeType::Lerp | NodeType::Clamp | NodeType::Smoothstep
763            | NodeType::Remap | NodeType::Step => {
764                vec![Socket::output("result", DataType::Float)]
765            }
766
767            // Effects
768            NodeType::Fresnel => vec![Socket::output("factor", DataType::Float)],
769            NodeType::Dissolve => vec![
770                Socket::output("color", DataType::Vec4),
771                Socket::output("mask", DataType::Float),
772            ],
773            NodeType::Distortion => vec![Socket::output("uv", DataType::Vec2)],
774            NodeType::Blur => vec![Socket::output("color", DataType::Vec4)],
775            NodeType::Sharpen => vec![Socket::output("color", DataType::Vec4)],
776            NodeType::EdgeDetect => vec![Socket::output("edges", DataType::Float)],
777            NodeType::Outline => vec![Socket::output("color", DataType::Vec4)],
778            NodeType::Bloom => vec![
779                Socket::output("color", DataType::Vec4),
780                Socket::output("bloom_mask", DataType::Float),
781            ],
782            NodeType::ChromaticAberration => vec![Socket::output("color", DataType::Vec4)],
783
784            // Color
785            NodeType::HSVToRGB => vec![Socket::output("rgb", DataType::Vec3)],
786            NodeType::RGBToHSV => vec![
787                Socket::output("h", DataType::Float),
788                Socket::output("s", DataType::Float),
789                Socket::output("v", DataType::Float),
790            ],
791            NodeType::Contrast | NodeType::Saturation | NodeType::Hue
792            | NodeType::Invert | NodeType::Posterize => {
793                vec![Socket::output("color", DataType::Vec3)]
794            }
795            NodeType::GradientMap => vec![Socket::output("color", DataType::Vec3)],
796
797            // Noise
798            NodeType::Perlin | NodeType::Simplex => vec![
799                Socket::output("value", DataType::Float),
800                Socket::output("gradient", DataType::Vec3),
801            ],
802            NodeType::Voronoi => vec![
803                Socket::output("distance", DataType::Float),
804                Socket::output("cell_id", DataType::Float),
805                Socket::output("cell_pos", DataType::Vec3),
806            ],
807            NodeType::FBM | NodeType::Turbulence => vec![
808                Socket::output("value", DataType::Float),
809            ],
810
811            // Outputs (terminal, no outputs)
812            NodeType::MainColor | NodeType::EmissionBuffer
813            | NodeType::BloomBuffer | NodeType::NormalOutput => vec![],
814        }
815    }
816
817    /// Generate the GLSL code snippet for this node.
818    ///
819    /// `var_prefix` is the unique variable name prefix for this node instance (e.g., "n42").
820    /// `input_vars` maps input socket index to the GLSL expression feeding it.
821    ///
822    /// Returns a vector of (line_of_code, output_socket_index).
823    pub fn generate_glsl(&self, var_prefix: &str, input_vars: &[String]) -> GlslSnippet {
824        let mut lines = Vec::new();
825        let mut outputs: Vec<String> = Vec::new();
826
827        match self {
828            // ── Sources ─────────────────────────────────────
829            NodeType::Color => {
830                let inp = input_or_default(input_vars, 0, "vec4(1.0, 1.0, 1.0, 1.0)");
831                let out = format!("{}_color", var_prefix);
832                lines.push(format!("vec4 {} = {};", out, inp));
833                outputs.push(out);
834            }
835            NodeType::Texture => {
836                let uv = input_or_default(input_vars, 0, "v_uv");
837                let sampler = input_or_default(input_vars, 1, "u_texture0");
838                let out = format!("{}_tex", var_prefix);
839                lines.push(format!("vec4 {} = texture2D({}, {});", out, sampler, uv));
840                outputs.push(format!("{}", out));
841                outputs.push(format!("{}.r", out));
842                outputs.push(format!("{}.g", out));
843                outputs.push(format!("{}.b", out));
844                outputs.push(format!("{}.a", out));
845            }
846            NodeType::VertexPosition => {
847                let out = format!("{}_vpos", var_prefix);
848                lines.push(format!("vec3 {} = v_position;", out));
849                outputs.push(out);
850            }
851            NodeType::VertexNormal => {
852                let out = format!("{}_vnorm", var_prefix);
853                lines.push(format!("vec3 {} = v_normal;", out));
854                outputs.push(out);
855            }
856            NodeType::Time => {
857                let speed = input_or_default(input_vars, 0, "1.0");
858                let t = format!("{}_t", var_prefix);
859                lines.push(format!("float {} = u_time * {};", t, speed));
860                outputs.push(t.clone());
861                outputs.push(format!("sin({})", t));
862                outputs.push(format!("cos({})", t));
863                outputs.push(format!("fract({})", t));
864            }
865            NodeType::CameraPos => {
866                let out = format!("{}_campos", var_prefix);
867                lines.push(format!("vec3 {} = u_camera_pos;", out));
868                outputs.push(out);
869            }
870            NodeType::GameStateVar => {
871                let var_name = input_or_default(input_vars, 0, "game_var_0");
872                let out = format!("{}_gsv", var_prefix);
873                // Game state vars are bound as uniforms named u_gs_<var_name>
874                let uniform_name = if var_name.starts_with("u_gs_") {
875                    var_name.clone()
876                } else {
877                    format!("u_gs_{}", var_name.trim_matches('"'))
878                };
879                lines.push(format!("float {} = {};", out, uniform_name));
880                outputs.push(out);
881            }
882
883            // ── Transforms ──────────────────────────────────
884            NodeType::Translate => {
885                let pos = input_or_default(input_vars, 0, "vec3(0.0)");
886                let offset = input_or_default(input_vars, 1, "vec3(0.0)");
887                let out = format!("{}_trans", var_prefix);
888                lines.push(format!("vec3 {} = {} + {};", out, pos, offset));
889                outputs.push(out);
890            }
891            NodeType::Rotate => {
892                let pos = input_or_default(input_vars, 0, "vec3(0.0)");
893                let axis = input_or_default(input_vars, 1, "vec3(0.0, 1.0, 0.0)");
894                let angle = input_or_default(input_vars, 2, "0.0");
895                let out = format!("{}_rot", var_prefix);
896                // Rodrigues' rotation formula
897                lines.push(format!("vec3 {out}_k = normalize({axis});"));
898                lines.push(format!("float {out}_c = cos({angle});"));
899                lines.push(format!("float {out}_s = sin({angle});"));
900                lines.push(format!(
901                    "vec3 {out} = {pos} * {out}_c + cross({out}_k, {pos}) * {out}_s + {out}_k * dot({out}_k, {pos}) * (1.0 - {out}_c);",
902                ));
903                outputs.push(out);
904            }
905            NodeType::Scale => {
906                let pos = input_or_default(input_vars, 0, "vec3(0.0)");
907                let factor = input_or_default(input_vars, 1, "vec3(1.0)");
908                let out = format!("{}_scl", var_prefix);
909                lines.push(format!("vec3 {} = {} * {};", out, pos, factor));
910                outputs.push(out);
911            }
912            NodeType::WorldToLocal => {
913                let pos = input_or_default(input_vars, 0, "vec3(0.0)");
914                let mat = input_or_default(input_vars, 1, "u_inv_model");
915                let out = format!("{}_w2l", var_prefix);
916                lines.push(format!("vec3 {} = ({} * vec4({}, 1.0)).xyz;", out, mat, pos));
917                outputs.push(out);
918            }
919            NodeType::LocalToWorld => {
920                let pos = input_or_default(input_vars, 0, "vec3(0.0)");
921                let mat = input_or_default(input_vars, 1, "u_model");
922                let out = format!("{}_l2w", var_prefix);
923                lines.push(format!("vec3 {} = ({} * vec4({}, 1.0)).xyz;", out, mat, pos));
924                outputs.push(out);
925            }
926
927            // ── Math ────────────────────────────────────────
928            NodeType::Add => {
929                let a = input_or_default(input_vars, 0, "0.0");
930                let b = input_or_default(input_vars, 1, "0.0");
931                let out = format!("{}_add", var_prefix);
932                lines.push(format!("float {} = {} + {};", out, a, b));
933                outputs.push(out);
934            }
935            NodeType::Sub => {
936                let a = input_or_default(input_vars, 0, "0.0");
937                let b = input_or_default(input_vars, 1, "0.0");
938                let out = format!("{}_sub", var_prefix);
939                lines.push(format!("float {} = {} - {};", out, a, b));
940                outputs.push(out);
941            }
942            NodeType::Mul => {
943                let a = input_or_default(input_vars, 0, "1.0");
944                let b = input_or_default(input_vars, 1, "1.0");
945                let out = format!("{}_mul", var_prefix);
946                lines.push(format!("float {} = {} * {};", out, a, b));
947                outputs.push(out);
948            }
949            NodeType::Div => {
950                let a = input_or_default(input_vars, 0, "1.0");
951                let b = input_or_default(input_vars, 1, "1.0");
952                let out = format!("{}_div", var_prefix);
953                lines.push(format!("float {} = {} / max({}, 0.0001);", out, a, b));
954                outputs.push(out);
955            }
956            NodeType::Dot => {
957                let a = input_or_default(input_vars, 0, "vec3(0.0)");
958                let b = input_or_default(input_vars, 1, "vec3(0.0)");
959                let out = format!("{}_dot", var_prefix);
960                lines.push(format!("float {} = dot({}, {});", out, a, b));
961                outputs.push(out);
962            }
963            NodeType::Cross => {
964                let a = input_or_default(input_vars, 0, "vec3(0.0)");
965                let b = input_or_default(input_vars, 1, "vec3(0.0)");
966                let out = format!("{}_cross", var_prefix);
967                lines.push(format!("vec3 {} = cross({}, {});", out, a, b));
968                outputs.push(out);
969            }
970            NodeType::Normalize => {
971                let v = input_or_default(input_vars, 0, "vec3(0.0, 1.0, 0.0)");
972                let out = format!("{}_norm", var_prefix);
973                lines.push(format!("vec3 {} = normalize({});", out, v));
974                outputs.push(out);
975            }
976            NodeType::Length => {
977                let v = input_or_default(input_vars, 0, "vec3(0.0)");
978                let out = format!("{}_len", var_prefix);
979                lines.push(format!("float {} = length({});", out, v));
980                outputs.push(out);
981            }
982            NodeType::Abs => {
983                let x = input_or_default(input_vars, 0, "0.0");
984                let out = format!("{}_abs", var_prefix);
985                lines.push(format!("float {} = abs({});", out, x));
986                outputs.push(out);
987            }
988            NodeType::Floor => {
989                let x = input_or_default(input_vars, 0, "0.0");
990                let out = format!("{}_floor", var_prefix);
991                lines.push(format!("float {} = floor({});", out, x));
992                outputs.push(out);
993            }
994            NodeType::Ceil => {
995                let x = input_or_default(input_vars, 0, "0.0");
996                let out = format!("{}_ceil", var_prefix);
997                lines.push(format!("float {} = ceil({});", out, x));
998                outputs.push(out);
999            }
1000            NodeType::Fract => {
1001                let x = input_or_default(input_vars, 0, "0.0");
1002                let out = format!("{}_fract", var_prefix);
1003                lines.push(format!("float {} = fract({});", out, x));
1004                outputs.push(out);
1005            }
1006            NodeType::Mod => {
1007                let x = input_or_default(input_vars, 0, "0.0");
1008                let y = input_or_default(input_vars, 1, "1.0");
1009                let out = format!("{}_mod", var_prefix);
1010                lines.push(format!("float {} = mod({}, {});", out, x, y));
1011                outputs.push(out);
1012            }
1013            NodeType::Pow => {
1014                let base = input_or_default(input_vars, 0, "1.0");
1015                let exp = input_or_default(input_vars, 1, "1.0");
1016                let out = format!("{}_pow", var_prefix);
1017                lines.push(format!("float {} = pow(max({}, 0.0), {});", out, base, exp));
1018                outputs.push(out);
1019            }
1020            NodeType::Sqrt => {
1021                let x = input_or_default(input_vars, 0, "1.0");
1022                let out = format!("{}_sqrt", var_prefix);
1023                lines.push(format!("float {} = sqrt(max({}, 0.0));", out, x));
1024                outputs.push(out);
1025            }
1026            NodeType::Sin => {
1027                let x = input_or_default(input_vars, 0, "0.0");
1028                let out = format!("{}_sin", var_prefix);
1029                lines.push(format!("float {} = sin({});", out, x));
1030                outputs.push(out);
1031            }
1032            NodeType::Cos => {
1033                let x = input_or_default(input_vars, 0, "0.0");
1034                let out = format!("{}_cos", var_prefix);
1035                lines.push(format!("float {} = cos({});", out, x));
1036                outputs.push(out);
1037            }
1038            NodeType::Tan => {
1039                let x = input_or_default(input_vars, 0, "0.0");
1040                let out = format!("{}_tan", var_prefix);
1041                lines.push(format!("float {} = tan({});", out, x));
1042                outputs.push(out);
1043            }
1044            NodeType::Atan2 => {
1045                let y = input_or_default(input_vars, 0, "0.0");
1046                let x = input_or_default(input_vars, 1, "1.0");
1047                let out = format!("{}_atan2", var_prefix);
1048                lines.push(format!("float {} = atan({}, {});", out, y, x));
1049                outputs.push(out);
1050            }
1051            NodeType::Lerp => {
1052                let a = input_or_default(input_vars, 0, "0.0");
1053                let b = input_or_default(input_vars, 1, "1.0");
1054                let t = input_or_default(input_vars, 2, "0.5");
1055                let out = format!("{}_lerp", var_prefix);
1056                lines.push(format!("float {} = mix({}, {}, {});", out, a, b, t));
1057                outputs.push(out);
1058            }
1059            NodeType::Clamp => {
1060                let x = input_or_default(input_vars, 0, "0.0");
1061                let lo = input_or_default(input_vars, 1, "0.0");
1062                let hi = input_or_default(input_vars, 2, "1.0");
1063                let out = format!("{}_clamp", var_prefix);
1064                lines.push(format!("float {} = clamp({}, {}, {});", out, x, lo, hi));
1065                outputs.push(out);
1066            }
1067            NodeType::Smoothstep => {
1068                let e0 = input_or_default(input_vars, 0, "0.0");
1069                let e1 = input_or_default(input_vars, 1, "1.0");
1070                let x = input_or_default(input_vars, 2, "0.5");
1071                let out = format!("{}_ss", var_prefix);
1072                lines.push(format!("float {} = smoothstep({}, {}, {});", out, e0, e1, x));
1073                outputs.push(out);
1074            }
1075            NodeType::Remap => {
1076                let x = input_or_default(input_vars, 0, "0.5");
1077                let in_min = input_or_default(input_vars, 1, "0.0");
1078                let in_max = input_or_default(input_vars, 2, "1.0");
1079                let out_min = input_or_default(input_vars, 3, "0.0");
1080                let out_max = input_or_default(input_vars, 4, "1.0");
1081                let out = format!("{}_remap", var_prefix);
1082                lines.push(format!(
1083                    "float {} = {} + ({} - {}) * (({}) - {}) / max(({}) - {}, 0.0001);",
1084                    out, out_min, out_max, out_min, x, in_min, in_max, in_min
1085                ));
1086                outputs.push(out);
1087            }
1088            NodeType::Step => {
1089                let edge = input_or_default(input_vars, 0, "0.5");
1090                let x = input_or_default(input_vars, 1, "0.0");
1091                let out = format!("{}_step", var_prefix);
1092                lines.push(format!("float {} = step({}, {});", out, edge, x));
1093                outputs.push(out);
1094            }
1095
1096            // ── Effects ─────────────────────────────────────
1097            NodeType::Fresnel => {
1098                let normal = input_or_default(input_vars, 0, "v_normal");
1099                let view = input_or_default(input_vars, 1, "normalize(u_camera_pos - v_position)");
1100                let power = input_or_default(input_vars, 2, "2.0");
1101                let bias = input_or_default(input_vars, 3, "0.0");
1102                let out = format!("{}_fresnel", var_prefix);
1103                lines.push(format!(
1104                    "float {} = {} + (1.0 - {}) * pow(1.0 - max(dot({}, {}), 0.0), {});",
1105                    out, bias, bias, normal, view, power
1106                ));
1107                outputs.push(out);
1108            }
1109            NodeType::Dissolve => {
1110                let color = input_or_default(input_vars, 0, "vec4(1.0)");
1111                let noise = input_or_default(input_vars, 1, "0.5");
1112                let thresh = input_or_default(input_vars, 2, "0.5");
1113                let edge_w = input_or_default(input_vars, 3, "0.05");
1114                let edge_c = input_or_default(input_vars, 4, "vec4(1.0, 0.5, 0.0, 1.0)");
1115                let out = format!("{}_diss", var_prefix);
1116                lines.push(format!("float {out}_mask = step({thresh}, {noise});"));
1117                lines.push(format!(
1118                    "float {out}_edge = smoothstep({thresh} - {edge_w}, {thresh}, {noise}) - {out}_mask;"
1119                ));
1120                lines.push(format!(
1121                    "vec4 {out} = mix({edge_c}, {color}, {out}_mask) * ({out}_mask + {out}_edge);"
1122                ));
1123                outputs.push(out.clone());
1124                outputs.push(format!("{}_mask", out));
1125            }
1126            NodeType::Distortion => {
1127                let uv = input_or_default(input_vars, 0, "v_uv");
1128                let strength = input_or_default(input_vars, 1, "0.1");
1129                let dir = input_or_default(input_vars, 2, "vec2(1.0, 0.0)");
1130                let noise = input_or_default(input_vars, 3, "0.0");
1131                let out = format!("{}_dist", var_prefix);
1132                lines.push(format!(
1133                    "vec2 {} = {} + {} * {} * (1.0 + {});",
1134                    out, uv, dir, strength, noise
1135                ));
1136                outputs.push(out);
1137            }
1138            NodeType::Blur => {
1139                let uv = input_or_default(input_vars, 0, "v_uv");
1140                let radius = input_or_default(input_vars, 1, "2.0");
1141                let _samples = input_or_default(input_vars, 2, "8");
1142                let sampler = input_or_default(input_vars, 3, "u_texture0");
1143                let out = format!("{}_blur", var_prefix);
1144                lines.push(format!("vec4 {out} = vec4(0.0);"));
1145                lines.push(format!("float {out}_total = 0.0;"));
1146                // Unrolled 9-tap gaussian
1147                lines.push(format!("for (int i = -4; i <= 4; i++) {{"));
1148                lines.push(format!("  for (int j = -4; j <= 4; j++) {{"));
1149                lines.push(format!("    vec2 {out}_off = vec2(float(i), float(j)) * {radius} / 512.0;"));
1150                lines.push(format!("    float {out}_w = exp(-float(i*i + j*j) / (2.0 * {radius} * {radius}));"));
1151                lines.push(format!("    {out} += texture2D({sampler}, {uv} + {out}_off) * {out}_w;"));
1152                lines.push(format!("    {out}_total += {out}_w;"));
1153                lines.push(format!("  }}"));
1154                lines.push(format!("}}"));
1155                lines.push(format!("{out} /= max({out}_total, 0.001);"));
1156                outputs.push(out);
1157            }
1158            NodeType::Sharpen => {
1159                let uv = input_or_default(input_vars, 0, "v_uv");
1160                let strength = input_or_default(input_vars, 1, "1.0");
1161                let sampler = input_or_default(input_vars, 2, "u_texture0");
1162                let out = format!("{}_sharp", var_prefix);
1163                lines.push(format!("vec2 {out}_px = vec2(1.0/512.0, 1.0/512.0);"));
1164                lines.push(format!("vec4 {out}_c = texture2D({sampler}, {uv});"));
1165                lines.push(format!("vec4 {out}_n = texture2D({sampler}, {uv} + vec2(0.0, {out}_px.y));"));
1166                lines.push(format!("vec4 {out}_s = texture2D({sampler}, {uv} - vec2(0.0, {out}_px.y));"));
1167                lines.push(format!("vec4 {out}_e = texture2D({sampler}, {uv} + vec2({out}_px.x, 0.0));"));
1168                lines.push(format!("vec4 {out}_w = texture2D({sampler}, {uv} - vec2({out}_px.x, 0.0));"));
1169                lines.push(format!(
1170                    "vec4 {out} = {out}_c + ({out}_c * 4.0 - {out}_n - {out}_s - {out}_e - {out}_w) * {strength};"
1171                ));
1172                outputs.push(out);
1173            }
1174            NodeType::EdgeDetect => {
1175                let uv = input_or_default(input_vars, 0, "v_uv");
1176                let threshold = input_or_default(input_vars, 1, "0.1");
1177                let sampler = input_or_default(input_vars, 2, "u_texture0");
1178                let out = format!("{}_edge", var_prefix);
1179                lines.push(format!("vec2 {out}_px = vec2(1.0/512.0);"));
1180                lines.push(format!("float {out}_tl = dot(texture2D({sampler}, {uv} + vec2(-1.0, 1.0)*{out}_px).rgb, vec3(0.299, 0.587, 0.114));"));
1181                lines.push(format!("float {out}_t  = dot(texture2D({sampler}, {uv} + vec2( 0.0, 1.0)*{out}_px).rgb, vec3(0.299, 0.587, 0.114));"));
1182                lines.push(format!("float {out}_tr = dot(texture2D({sampler}, {uv} + vec2( 1.0, 1.0)*{out}_px).rgb, vec3(0.299, 0.587, 0.114));"));
1183                lines.push(format!("float {out}_l  = dot(texture2D({sampler}, {uv} + vec2(-1.0, 0.0)*{out}_px).rgb, vec3(0.299, 0.587, 0.114));"));
1184                lines.push(format!("float {out}_r  = dot(texture2D({sampler}, {uv} + vec2( 1.0, 0.0)*{out}_px).rgb, vec3(0.299, 0.587, 0.114));"));
1185                lines.push(format!("float {out}_bl = dot(texture2D({sampler}, {uv} + vec2(-1.0,-1.0)*{out}_px).rgb, vec3(0.299, 0.587, 0.114));"));
1186                lines.push(format!("float {out}_b  = dot(texture2D({sampler}, {uv} + vec2( 0.0,-1.0)*{out}_px).rgb, vec3(0.299, 0.587, 0.114));"));
1187                lines.push(format!("float {out}_br = dot(texture2D({sampler}, {uv} + vec2( 1.0,-1.0)*{out}_px).rgb, vec3(0.299, 0.587, 0.114));"));
1188                lines.push(format!("float {out}_gx = -1.0*{out}_tl + 1.0*{out}_tr - 2.0*{out}_l + 2.0*{out}_r - 1.0*{out}_bl + 1.0*{out}_br;"));
1189                lines.push(format!("float {out}_gy = -1.0*{out}_tl - 2.0*{out}_t - 1.0*{out}_tr + 1.0*{out}_bl + 2.0*{out}_b + 1.0*{out}_br;"));
1190                lines.push(format!("float {out} = step({threshold}, sqrt({out}_gx*{out}_gx + {out}_gy*{out}_gy));"));
1191                outputs.push(out);
1192            }
1193            NodeType::Outline => {
1194                let color = input_or_default(input_vars, 0, "vec4(1.0)");
1195                let _depth = input_or_default(input_vars, 1, "0.0");
1196                let normal = input_or_default(input_vars, 2, "v_normal");
1197                let width = input_or_default(input_vars, 3, "1.0");
1198                let outline_c = input_or_default(input_vars, 4, "vec4(0.0, 0.0, 0.0, 1.0)");
1199                let out = format!("{}_outline", var_prefix);
1200                lines.push(format!(
1201                    "float {out}_rim = 1.0 - abs(dot(normalize({normal}), normalize(u_camera_pos - v_position)));"
1202                ));
1203                lines.push(format!("float {out}_factor = smoothstep(1.0 - {width} * 0.1, 1.0, {out}_rim);"));
1204                lines.push(format!("vec4 {out} = mix({color}, {outline_c}, {out}_factor);"));
1205                outputs.push(out);
1206            }
1207            NodeType::Bloom => {
1208                let color = input_or_default(input_vars, 0, "vec4(1.0)");
1209                let threshold = input_or_default(input_vars, 1, "0.8");
1210                let intensity = input_or_default(input_vars, 2, "1.5");
1211                let _radius = input_or_default(input_vars, 3, "4.0");
1212                let out = format!("{}_bloom", var_prefix);
1213                lines.push(format!(
1214                    "float {out}_lum = dot({color}.rgb, vec3(0.299, 0.587, 0.114));"
1215                ));
1216                lines.push(format!(
1217                    "float {out}_mask = max(0.0, {out}_lum - {threshold}) / max(1.0 - {threshold}, 0.001);"
1218                ));
1219                lines.push(format!("vec4 {out} = {color} + {color} * {out}_mask * {intensity};"));
1220                outputs.push(out.clone());
1221                outputs.push(format!("{}_mask", out));
1222            }
1223            NodeType::ChromaticAberration => {
1224                let uv = input_or_default(input_vars, 0, "v_uv");
1225                let offset = input_or_default(input_vars, 1, "0.005");
1226                let sampler = input_or_default(input_vars, 2, "u_texture0");
1227                let out = format!("{}_ca", var_prefix);
1228                lines.push(format!("vec2 {out}_dir = normalize({uv} - vec2(0.5));"));
1229                lines.push(format!("float {out}_r = texture2D({sampler}, {uv} + {out}_dir * {offset}).r;"));
1230                lines.push(format!("float {out}_g = texture2D({sampler}, {uv}).g;"));
1231                lines.push(format!("float {out}_b = texture2D({sampler}, {uv} - {out}_dir * {offset}).b;"));
1232                lines.push(format!("float {out}_a = texture2D({sampler}, {uv}).a;"));
1233                lines.push(format!("vec4 {out} = vec4({out}_r, {out}_g, {out}_b, {out}_a);"));
1234                outputs.push(out);
1235            }
1236
1237            // ── Color ───────────────────────────────────────
1238            NodeType::HSVToRGB => {
1239                let h = input_or_default(input_vars, 0, "0.0");
1240                let s = input_or_default(input_vars, 1, "1.0");
1241                let v = input_or_default(input_vars, 2, "1.0");
1242                let out = format!("{}_h2r", var_prefix);
1243                lines.push(format!("vec3 {out}_k = vec3(1.0, 2.0/3.0, 1.0/3.0);"));
1244                lines.push(format!("vec3 {out}_p = abs(fract(vec3({h}) + {out}_k) * 6.0 - 3.0);"));
1245                lines.push(format!("vec3 {out} = {v} * mix(vec3(1.0), clamp({out}_p - 1.0, 0.0, 1.0), {s});"));
1246                outputs.push(out);
1247            }
1248            NodeType::RGBToHSV => {
1249                let rgb = input_or_default(input_vars, 0, "vec3(1.0)");
1250                let out = format!("{}_r2h", var_prefix);
1251                lines.push(format!("vec4 {out}_K = vec4(0.0, -1.0/3.0, 2.0/3.0, -1.0);"));
1252                lines.push(format!("vec4 {out}_p = mix(vec4({rgb}.bg, {out}_K.wz), vec4({rgb}.gb, {out}_K.xy), step({rgb}.b, {rgb}.g));"));
1253                lines.push(format!("vec4 {out}_q = mix(vec4({out}_p.xyw, {rgb}.r), vec4({rgb}.r, {out}_p.yzx), step({out}_p.x, {rgb}.r));"));
1254                lines.push(format!("float {out}_d = {out}_q.x - min({out}_q.w, {out}_q.y);"));
1255                lines.push(format!("float {out}_e = 1.0e-10;"));
1256                lines.push(format!("float {out}_h = abs({out}_q.z + ({out}_q.w - {out}_q.y) / (6.0 * {out}_d + {out}_e));"));
1257                lines.push(format!("float {out}_s = {out}_d / ({out}_q.x + {out}_e);"));
1258                lines.push(format!("float {out}_v = {out}_q.x;"));
1259                outputs.push(format!("{}_h", out));
1260                outputs.push(format!("{}_s", out));
1261                outputs.push(format!("{}_v", out));
1262            }
1263            NodeType::Contrast => {
1264                let color = input_or_default(input_vars, 0, "vec3(0.5)");
1265                let amount = input_or_default(input_vars, 1, "1.0");
1266                let out = format!("{}_contrast", var_prefix);
1267                lines.push(format!("vec3 {out} = (({color} - 0.5) * {amount}) + 0.5;"));
1268                outputs.push(out);
1269            }
1270            NodeType::Saturation => {
1271                let color = input_or_default(input_vars, 0, "vec3(0.5)");
1272                let amount = input_or_default(input_vars, 1, "1.0");
1273                let out = format!("{}_sat", var_prefix);
1274                lines.push(format!("float {out}_lum = dot({color}, vec3(0.299, 0.587, 0.114));"));
1275                lines.push(format!("vec3 {out} = mix(vec3({out}_lum), {color}, {amount});"));
1276                outputs.push(out);
1277            }
1278            NodeType::Hue => {
1279                let color = input_or_default(input_vars, 0, "vec3(0.5)");
1280                let shift = input_or_default(input_vars, 1, "0.0");
1281                let out = format!("{}_hue", var_prefix);
1282                // Simple hue rotation via angle in YIQ-like space
1283                lines.push(format!("float {out}_angle = {shift} * 6.28318;"));
1284                lines.push(format!("float {out}_cs = cos({out}_angle);"));
1285                lines.push(format!("float {out}_sn = sin({out}_angle);"));
1286                lines.push(format!("vec3 {out}_w = vec3(0.299, 0.587, 0.114);"));
1287                lines.push(format!("vec3 {out} = vec3("));
1288                lines.push(format!("  dot({color}, {out}_w + vec3(1.0-0.299, -0.587, -0.114)*{out}_cs + vec3(0.0, 0.0, 1.0)*{out}_sn*0.5),"));
1289                lines.push(format!("  dot({color}, {out}_w + vec3(-0.299, 1.0-0.587, -0.114)*{out}_cs + vec3(0.0, 0.0, -0.5)*{out}_sn),"));
1290                lines.push(format!("  dot({color}, {out}_w + vec3(-0.299, -0.587, 1.0-0.114)*{out}_cs + vec3(0.0, 1.0, 0.0)*{out}_sn*0.5)"));
1291                lines.push(format!(");"));
1292                outputs.push(out);
1293            }
1294            NodeType::Invert => {
1295                let color = input_or_default(input_vars, 0, "vec3(0.5)");
1296                let out = format!("{}_inv", var_prefix);
1297                lines.push(format!("vec3 {out} = 1.0 - {color};"));
1298                outputs.push(out);
1299            }
1300            NodeType::Posterize => {
1301                let color = input_or_default(input_vars, 0, "vec3(0.5)");
1302                let levels = input_or_default(input_vars, 1, "4.0");
1303                let out = format!("{}_poster", var_prefix);
1304                lines.push(format!("vec3 {out} = floor({color} * {levels}) / {levels};"));
1305                outputs.push(out);
1306            }
1307            NodeType::GradientMap => {
1308                let t = input_or_default(input_vars, 0, "0.5");
1309                let color_a = input_or_default(input_vars, 1, "vec3(0.0)");
1310                let color_b = input_or_default(input_vars, 2, "vec3(1.0)");
1311                let out = format!("{}_gmap", var_prefix);
1312                lines.push(format!("vec3 {out} = mix({color_a}, {color_b}, clamp({t}, 0.0, 1.0));"));
1313                outputs.push(out);
1314            }
1315
1316            // ── Noise ───────────────────────────────────────
1317            NodeType::Perlin => {
1318                let pos = input_or_default(input_vars, 0, "v_position");
1319                let scale = input_or_default(input_vars, 1, "1.0");
1320                let seed = input_or_default(input_vars, 2, "0.0");
1321                let out = format!("{}_perlin", var_prefix);
1322                // Perlin noise implementation
1323                lines.push(format!("vec3 {out}_p = {pos} * {scale} + vec3({seed});"));
1324                lines.push(format!("vec3 {out}_i = floor({out}_p);"));
1325                lines.push(format!("vec3 {out}_f = fract({out}_p);"));
1326                lines.push(format!("vec3 {out}_u = {out}_f * {out}_f * (3.0 - 2.0 * {out}_f);"));
1327                // Hash function inline
1328                lines.push(format!("float {out}_h000 = fract(sin(dot({out}_i, vec3(127.1, 311.7, 74.7))) * 43758.5453);"));
1329                lines.push(format!("float {out}_h100 = fract(sin(dot({out}_i + vec3(1.0,0.0,0.0), vec3(127.1, 311.7, 74.7))) * 43758.5453);"));
1330                lines.push(format!("float {out}_h010 = fract(sin(dot({out}_i + vec3(0.0,1.0,0.0), vec3(127.1, 311.7, 74.7))) * 43758.5453);"));
1331                lines.push(format!("float {out}_h110 = fract(sin(dot({out}_i + vec3(1.0,1.0,0.0), vec3(127.1, 311.7, 74.7))) * 43758.5453);"));
1332                lines.push(format!("float {out}_h001 = fract(sin(dot({out}_i + vec3(0.0,0.0,1.0), vec3(127.1, 311.7, 74.7))) * 43758.5453);"));
1333                lines.push(format!("float {out}_h101 = fract(sin(dot({out}_i + vec3(1.0,0.0,1.0), vec3(127.1, 311.7, 74.7))) * 43758.5453);"));
1334                lines.push(format!("float {out}_h011 = fract(sin(dot({out}_i + vec3(0.0,1.0,1.0), vec3(127.1, 311.7, 74.7))) * 43758.5453);"));
1335                lines.push(format!("float {out}_h111 = fract(sin(dot({out}_i + vec3(1.0,1.0,1.0), vec3(127.1, 311.7, 74.7))) * 43758.5453);"));
1336                lines.push(format!("float {out}_x0 = mix(mix({out}_h000, {out}_h100, {out}_u.x), mix({out}_h010, {out}_h110, {out}_u.x), {out}_u.y);"));
1337                lines.push(format!("float {out}_x1 = mix(mix({out}_h001, {out}_h101, {out}_u.x), mix({out}_h011, {out}_h111, {out}_u.x), {out}_u.y);"));
1338                lines.push(format!("float {out}_val = mix({out}_x0, {out}_x1, {out}_u.z);"));
1339                lines.push(format!("vec3 {out}_grad = normalize(vec3({out}_h100 - {out}_h000, {out}_h010 - {out}_h000, {out}_h001 - {out}_h000));"));
1340                outputs.push(format!("{out}_val"));
1341                outputs.push(format!("{out}_grad"));
1342            }
1343            NodeType::Simplex => {
1344                let pos = input_or_default(input_vars, 0, "v_position");
1345                let scale = input_or_default(input_vars, 1, "1.0");
1346                let seed = input_or_default(input_vars, 2, "0.0");
1347                let out = format!("{}_simplex", var_prefix);
1348                // Simplex noise approximation (using hash-based approach)
1349                lines.push(format!("vec3 {out}_p = {pos} * {scale} + vec3({seed});"));
1350                lines.push(format!("float {out}_F3 = 1.0/3.0;"));
1351                lines.push(format!("float {out}_s = ({out}_p.x + {out}_p.y + {out}_p.z) * {out}_F3;"));
1352                lines.push(format!("vec3 {out}_i = floor({out}_p + vec3({out}_s));"));
1353                lines.push(format!("float {out}_G3 = 1.0/6.0;"));
1354                lines.push(format!("float {out}_t = ({out}_i.x + {out}_i.y + {out}_i.z) * {out}_G3;"));
1355                lines.push(format!("vec3 {out}_x0 = {out}_p - ({out}_i - vec3({out}_t));"));
1356                // Simplified: use hash-based value for approximation
1357                lines.push(format!("float {out}_val = fract(sin(dot({out}_i, vec3(127.1, 311.7, 74.7))) * 43758.5453);"));
1358                lines.push(format!("float {out}_val2 = fract(sin(dot({out}_i + 1.0, vec3(127.1, 311.7, 74.7))) * 43758.5453);"));
1359                lines.push(format!("float {out}_result = mix({out}_val, {out}_val2, fract({out}_s)) * 2.0 - 1.0;"));
1360                lines.push(format!("vec3 {out}_grad = normalize(vec3({out}_val, {out}_val2, {out}_result));"));
1361                outputs.push(format!("{out}_result"));
1362                outputs.push(format!("{out}_grad"));
1363            }
1364            NodeType::Voronoi => {
1365                let pos = input_or_default(input_vars, 0, "v_position");
1366                let scale = input_or_default(input_vars, 1, "1.0");
1367                let jitter = input_or_default(input_vars, 2, "1.0");
1368                let out = format!("{}_voronoi", var_prefix);
1369                lines.push(format!("vec3 {out}_p = {pos} * {scale};"));
1370                lines.push(format!("vec3 {out}_n = floor({out}_p);"));
1371                lines.push(format!("vec3 {out}_f = fract({out}_p);"));
1372                lines.push(format!("float {out}_md = 8.0;"));
1373                lines.push(format!("float {out}_id = 0.0;"));
1374                lines.push(format!("vec3 {out}_mr = vec3(0.0);"));
1375                lines.push(format!("for (int k = -1; k <= 1; k++) {{"));
1376                lines.push(format!("  for (int j = -1; j <= 1; j++) {{"));
1377                lines.push(format!("    for (int i = -1; i <= 1; i++) {{"));
1378                lines.push(format!("      vec3 {out}_g = vec3(float(i), float(j), float(k));"));
1379                lines.push(format!("      vec3 {out}_cell = {out}_n + {out}_g;"));
1380                lines.push(format!("      vec3 {out}_o = fract(sin(vec3(dot({out}_cell, vec3(127.1,311.7,74.7)), dot({out}_cell, vec3(269.5,183.3,246.1)), dot({out}_cell, vec3(113.5,271.9,124.6)))) * 43758.5453) * {jitter};"));
1381                lines.push(format!("      vec3 {out}_r = {out}_g + {out}_o - {out}_f;"));
1382                lines.push(format!("      float {out}_d = dot({out}_r, {out}_r);"));
1383                lines.push(format!("      if ({out}_d < {out}_md) {{"));
1384                lines.push(format!("        {out}_md = {out}_d;"));
1385                lines.push(format!("        {out}_id = dot({out}_cell, vec3(7.0, 157.0, 113.0));"));
1386                lines.push(format!("        {out}_mr = {out}_r;"));
1387                lines.push(format!("      }}"));
1388                lines.push(format!("    }}"));
1389                lines.push(format!("  }}"));
1390                lines.push(format!("}}"));
1391                outputs.push(format!("sqrt({out}_md)"));
1392                outputs.push(format!("fract({out}_id)"));
1393                outputs.push(format!("{out}_mr"));
1394            }
1395            NodeType::FBM => {
1396                let pos = input_or_default(input_vars, 0, "v_position");
1397                let scale = input_or_default(input_vars, 1, "1.0");
1398                let _octaves = input_or_default(input_vars, 2, "4");
1399                let lacunarity = input_or_default(input_vars, 3, "2.0");
1400                let gain = input_or_default(input_vars, 4, "0.5");
1401                let out = format!("{}_fbm", var_prefix);
1402                lines.push(format!("float {out} = 0.0;"));
1403                lines.push(format!("float {out}_amp = 1.0;"));
1404                lines.push(format!("vec3 {out}_p = {pos} * {scale};"));
1405                lines.push(format!("for (int {out}_i = 0; {out}_i < 4; {out}_i++) {{"));
1406                lines.push(format!("  vec3 {out}_fi = floor({out}_p);"));
1407                lines.push(format!("  vec3 {out}_ff = fract({out}_p);"));
1408                lines.push(format!("  vec3 {out}_fu = {out}_ff*{out}_ff*(3.0-2.0*{out}_ff);"));
1409                lines.push(format!("  float {out}_a = fract(sin(dot({out}_fi, vec3(127.1,311.7,74.7)))*43758.5453);"));
1410                lines.push(format!("  float {out}_b = fract(sin(dot({out}_fi+vec3(1,0,0), vec3(127.1,311.7,74.7)))*43758.5453);"));
1411                lines.push(format!("  float {out}_c = fract(sin(dot({out}_fi+vec3(0,1,0), vec3(127.1,311.7,74.7)))*43758.5453);"));
1412                lines.push(format!("  float {out}_d = fract(sin(dot({out}_fi+vec3(1,1,0), vec3(127.1,311.7,74.7)))*43758.5453);"));
1413                lines.push(format!("  float {out}_v = mix(mix({out}_a,{out}_b,{out}_fu.x), mix({out}_c,{out}_d,{out}_fu.x), {out}_fu.y);"));
1414                lines.push(format!("  {out} += {out}_amp * {out}_v;"));
1415                lines.push(format!("  {out}_amp *= {gain};"));
1416                lines.push(format!("  {out}_p *= {lacunarity};"));
1417                lines.push(format!("}}"));
1418                outputs.push(out);
1419            }
1420            NodeType::Turbulence => {
1421                let pos = input_or_default(input_vars, 0, "v_position");
1422                let scale = input_or_default(input_vars, 1, "1.0");
1423                let _octaves = input_or_default(input_vars, 2, "4");
1424                let lacunarity = input_or_default(input_vars, 3, "2.0");
1425                let gain = input_or_default(input_vars, 4, "0.5");
1426                let out = format!("{}_turb", var_prefix);
1427                lines.push(format!("float {out} = 0.0;"));
1428                lines.push(format!("float {out}_amp = 1.0;"));
1429                lines.push(format!("vec3 {out}_p = {pos} * {scale};"));
1430                lines.push(format!("for (int {out}_i = 0; {out}_i < 4; {out}_i++) {{"));
1431                lines.push(format!("  vec3 {out}_fi = floor({out}_p);"));
1432                lines.push(format!("  vec3 {out}_ff = fract({out}_p);"));
1433                lines.push(format!("  vec3 {out}_fu = {out}_ff*{out}_ff*(3.0-2.0*{out}_ff);"));
1434                lines.push(format!("  float {out}_a = fract(sin(dot({out}_fi, vec3(127.1,311.7,74.7)))*43758.5453);"));
1435                lines.push(format!("  float {out}_b = fract(sin(dot({out}_fi+vec3(1,0,0), vec3(127.1,311.7,74.7)))*43758.5453);"));
1436                lines.push(format!("  float {out}_c = fract(sin(dot({out}_fi+vec3(0,1,0), vec3(127.1,311.7,74.7)))*43758.5453);"));
1437                lines.push(format!("  float {out}_d = fract(sin(dot({out}_fi+vec3(1,1,0), vec3(127.1,311.7,74.7)))*43758.5453);"));
1438                lines.push(format!("  float {out}_v = mix(mix({out}_a,{out}_b,{out}_fu.x), mix({out}_c,{out}_d,{out}_fu.x), {out}_fu.y);"));
1439                lines.push(format!("  {out} += {out}_amp * abs({out}_v * 2.0 - 1.0);"));
1440                lines.push(format!("  {out}_amp *= {gain};"));
1441                lines.push(format!("  {out}_p *= {lacunarity};"));
1442                lines.push(format!("}}"));
1443                outputs.push(out);
1444            }
1445
1446            // ── Outputs ─────────────────────────────────────
1447            NodeType::MainColor => {
1448                let color = input_or_default(input_vars, 0, "vec4(1.0)");
1449                lines.push(format!("gl_FragColor = {};", color));
1450            }
1451            NodeType::EmissionBuffer => {
1452                let emission = input_or_default(input_vars, 0, "vec4(0.0)");
1453                lines.push(format!("gl_FragData[1] = {};", emission));
1454            }
1455            NodeType::BloomBuffer => {
1456                let bloom = input_or_default(input_vars, 0, "vec4(0.0)");
1457                lines.push(format!("gl_FragData[2] = {};", bloom));
1458            }
1459            NodeType::NormalOutput => {
1460                let normal = input_or_default(input_vars, 0, "v_normal");
1461                lines.push(format!("gl_FragData[3] = vec4({} * 0.5 + 0.5, 1.0);", normal));
1462            }
1463        }
1464
1465        GlslSnippet { lines, output_vars: outputs }
1466    }
1467}
1468
1469fn input_or_default(input_vars: &[String], index: usize, default: &str) -> String {
1470    if index < input_vars.len() && !input_vars[index].is_empty() {
1471        input_vars[index].clone()
1472    } else {
1473        default.to_string()
1474    }
1475}
1476
1477// ---------------------------------------------------------------------------
1478// GLSL snippet result
1479// ---------------------------------------------------------------------------
1480
1481/// Result of generating GLSL for a single node.
1482#[derive(Debug, Clone)]
1483pub struct GlslSnippet {
1484    /// Lines of GLSL code to insert.
1485    pub lines: Vec<String>,
1486    /// GLSL expressions for each output socket.
1487    pub output_vars: Vec<String>,
1488}
1489
1490// ---------------------------------------------------------------------------
1491// Shader Node — a concrete instance in a graph
1492// ---------------------------------------------------------------------------
1493
1494/// A single node instance in a shader graph.
1495#[derive(Debug, Clone)]
1496pub struct ShaderNode {
1497    pub id: NodeId,
1498    pub node_type: NodeType,
1499    pub label: String,
1500    pub inputs: Vec<Socket>,
1501    pub outputs: Vec<Socket>,
1502    /// Extra per-node properties keyed by name.
1503    pub properties: HashMap<String, ParamValue>,
1504    /// Position in the editor (for serialization).
1505    pub editor_x: f32,
1506    pub editor_y: f32,
1507    /// Whether this node is enabled. Disabled nodes are skipped during compilation.
1508    pub enabled: bool,
1509    /// Conditional: if set, this node is only active when the named game state variable
1510    /// exceeds the threshold.
1511    pub conditional_var: Option<String>,
1512    pub conditional_threshold: f32,
1513}
1514
1515impl ShaderNode {
1516    /// Create a new node with default sockets for the given type.
1517    pub fn new(id: NodeId, node_type: NodeType) -> Self {
1518        let inputs = node_type.default_inputs();
1519        let outputs = node_type.default_outputs();
1520        let label = node_type.display_name().to_string();
1521        Self {
1522            id,
1523            node_type,
1524            label,
1525            inputs,
1526            outputs,
1527            properties: HashMap::new(),
1528            editor_x: 0.0,
1529            editor_y: 0.0,
1530            enabled: true,
1531            conditional_var: None,
1532            conditional_threshold: 0.0,
1533        }
1534    }
1535
1536    /// Set editor position.
1537    pub fn at(mut self, x: f32, y: f32) -> Self {
1538        self.editor_x = x;
1539        self.editor_y = y;
1540        self
1541    }
1542
1543    /// Set a property value.
1544    pub fn with_property(mut self, key: &str, value: ParamValue) -> Self {
1545        self.properties.insert(key.to_string(), value);
1546        self
1547    }
1548
1549    /// Set a default value for an input socket by name.
1550    pub fn with_input_default(mut self, socket_name: &str, value: ParamValue) -> Self {
1551        for s in &mut self.inputs {
1552            if s.name == socket_name {
1553                s.default_value = Some(value.clone());
1554            }
1555        }
1556        self
1557    }
1558
1559    /// Set the conditional gate on this node.
1560    pub fn with_condition(mut self, var_name: &str, threshold: f32) -> Self {
1561        self.conditional_var = Some(var_name.to_string());
1562        self.conditional_threshold = threshold;
1563        self
1564    }
1565
1566    /// Get the output socket data type at the given index.
1567    pub fn output_type(&self, index: usize) -> Option<DataType> {
1568        self.outputs.get(index).map(|s| s.data_type)
1569    }
1570
1571    /// Get the input socket data type at the given index.
1572    pub fn input_type(&self, index: usize) -> Option<DataType> {
1573        self.inputs.get(index).map(|s| s.data_type)
1574    }
1575
1576    /// Get the default value for input socket at the given index.
1577    pub fn input_default(&self, index: usize) -> Option<&ParamValue> {
1578        self.inputs.get(index).and_then(|s| s.default_value.as_ref())
1579    }
1580
1581    /// Return the GLSL variable prefix for this node.
1582    pub fn var_prefix(&self) -> String {
1583        format!("n{}", self.id.0)
1584    }
1585
1586    /// Compute a rough cost estimate for this node.
1587    pub fn estimated_cost(&self) -> u32 {
1588        self.node_type.instruction_cost()
1589    }
1590}
1591
1592// ---------------------------------------------------------------------------
1593// Connection
1594// ---------------------------------------------------------------------------
1595
1596/// A connection between two sockets in the graph.
1597#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1598pub struct Connection {
1599    pub from_node: NodeId,
1600    pub from_socket: usize,
1601    pub to_node: NodeId,
1602    pub to_socket: usize,
1603}
1604
1605impl Connection {
1606    pub fn new(from_node: NodeId, from_socket: usize, to_node: NodeId, to_socket: usize) -> Self {
1607        Self { from_node, from_socket, to_node, to_socket }
1608    }
1609}
1610
1611// ---------------------------------------------------------------------------
1612// ShaderGraph — the full graph container
1613// ---------------------------------------------------------------------------
1614
1615/// A complete shader graph containing nodes and connections.
1616#[derive(Debug, Clone)]
1617pub struct ShaderGraph {
1618    pub name: String,
1619    nodes: HashMap<NodeId, ShaderNode>,
1620    connections: Vec<Connection>,
1621    next_id: u64,
1622}
1623
1624impl ShaderGraph {
1625    /// Create a new empty shader graph.
1626    pub fn new(name: &str) -> Self {
1627        Self {
1628            name: name.to_string(),
1629            nodes: HashMap::new(),
1630            connections: Vec::new(),
1631            next_id: 1,
1632        }
1633    }
1634
1635    /// Allocate a fresh node ID.
1636    pub fn alloc_id(&mut self) -> NodeId {
1637        let id = NodeId(self.next_id);
1638        self.next_id += 1;
1639        id
1640    }
1641
1642    /// Add a node to the graph, returning its ID.
1643    pub fn add_node(&mut self, node_type: NodeType) -> NodeId {
1644        let id = self.alloc_id();
1645        let node = ShaderNode::new(id, node_type);
1646        self.nodes.insert(id, node);
1647        id
1648    }
1649
1650    /// Add a pre-built node to the graph.
1651    pub fn add_node_with(&mut self, mut node: ShaderNode) -> NodeId {
1652        let id = self.alloc_id();
1653        node.id = id;
1654        self.nodes.insert(id, node);
1655        id
1656    }
1657
1658    /// Add a pre-built node, reusing its existing ID if set, or allocating one.
1659    pub fn insert_node(&mut self, node: ShaderNode) -> NodeId {
1660        let id = node.id;
1661        if id.0 >= self.next_id {
1662            self.next_id = id.0 + 1;
1663        }
1664        self.nodes.insert(id, node);
1665        id
1666    }
1667
1668    /// Connect an output socket of one node to an input socket of another.
1669    pub fn connect(&mut self, from_node: NodeId, from_socket: usize, to_node: NodeId, to_socket: usize) {
1670        // Remove any existing connection to this input socket
1671        self.connections.retain(|c| !(c.to_node == to_node && c.to_socket == to_socket));
1672        self.connections.push(Connection::new(from_node, from_socket, to_node, to_socket));
1673    }
1674
1675    /// Remove all connections involving a node.
1676    pub fn disconnect_node(&mut self, node_id: NodeId) {
1677        self.connections.retain(|c| c.from_node != node_id && c.to_node != node_id);
1678    }
1679
1680    /// Remove a specific connection.
1681    pub fn disconnect(&mut self, from_node: NodeId, from_socket: usize, to_node: NodeId, to_socket: usize) {
1682        self.connections.retain(|c| {
1683            !(c.from_node == from_node && c.from_socket == from_socket
1684              && c.to_node == to_node && c.to_socket == to_socket)
1685        });
1686    }
1687
1688    /// Remove a node and all its connections.
1689    pub fn remove_node(&mut self, node_id: NodeId) {
1690        self.disconnect_node(node_id);
1691        self.nodes.remove(&node_id);
1692    }
1693
1694    /// Get a reference to a node.
1695    pub fn node(&self, id: impl std::borrow::Borrow<NodeId>) -> Option<&ShaderNode> {
1696        self.nodes.get(id.borrow())
1697    }
1698
1699    /// Get a mutable reference to a node.
1700    pub fn node_mut(&mut self, id: NodeId) -> Option<&mut ShaderNode> {
1701        self.nodes.get_mut(&id)
1702    }
1703
1704    /// Iterate over all nodes.
1705    pub fn nodes(&self) -> impl Iterator<Item = &ShaderNode> {
1706        self.nodes.values()
1707    }
1708
1709    /// Iterate over all node IDs.
1710    pub fn node_ids(&self) -> impl Iterator<Item = NodeId> + '_ {
1711        self.nodes.keys().copied()
1712    }
1713
1714    /// Number of nodes.
1715    pub fn node_count(&self) -> usize {
1716        self.nodes.len()
1717    }
1718
1719    /// Get all connections.
1720    pub fn connections(&self) -> &[Connection] {
1721        &self.connections
1722    }
1723
1724    /// Get connections feeding into a specific node's input socket.
1725    pub fn incoming_connections(&self, node_id: NodeId) -> Vec<&Connection> {
1726        self.connections.iter().filter(|c| c.to_node == node_id).collect()
1727    }
1728
1729    /// Get connections going out of a specific node.
1730    pub fn outgoing_connections(&self, node_id: NodeId) -> Vec<&Connection> {
1731        self.connections.iter().filter(|c| c.from_node == node_id).collect()
1732    }
1733
1734    /// Find all output nodes (MainColor, EmissionBuffer, BloomBuffer, NormalOutput).
1735    pub fn output_nodes(&self) -> Vec<NodeId> {
1736        self.nodes.values()
1737            .filter(|n| n.node_type.is_output())
1738            .map(|n| n.id)
1739            .collect()
1740    }
1741
1742    /// Find all source nodes (no required input connections).
1743    pub fn source_nodes(&self) -> Vec<NodeId> {
1744        self.nodes.values()
1745            .filter(|n| n.node_type.is_source())
1746            .map(|n| n.id)
1747            .collect()
1748    }
1749
1750    /// Compute the total estimated instruction cost of the graph.
1751    pub fn estimated_cost(&self) -> u32 {
1752        self.nodes.values().map(|n| n.estimated_cost()).sum()
1753    }
1754
1755    /// Validate basic graph integrity: all connections reference existing nodes,
1756    /// socket indices are in range, no self-loops.
1757    pub fn validate(&self) -> Vec<String> {
1758        let mut errors = Vec::new();
1759        for conn in &self.connections {
1760            if !self.nodes.contains_key(&conn.from_node) {
1761                errors.push(format!("Connection references missing source node {}", conn.from_node.0));
1762            }
1763            if !self.nodes.contains_key(&conn.to_node) {
1764                errors.push(format!("Connection references missing target node {}", conn.to_node.0));
1765            }
1766            if conn.from_node == conn.to_node {
1767                errors.push(format!("Self-loop on node {}", conn.from_node.0));
1768            }
1769            if let Some(src) = self.nodes.get(&conn.from_node) {
1770                if conn.from_socket >= src.outputs.len() {
1771                    errors.push(format!(
1772                        "Node {} output socket {} out of range (has {})",
1773                        conn.from_node.0, conn.from_socket, src.outputs.len()
1774                    ));
1775                }
1776            }
1777            if let Some(dst) = self.nodes.get(&conn.to_node) {
1778                if conn.to_socket >= dst.inputs.len() {
1779                    errors.push(format!(
1780                        "Node {} input socket {} out of range (has {})",
1781                        conn.to_node.0, conn.to_socket, dst.inputs.len()
1782                    ));
1783                }
1784            }
1785        }
1786        // Check for duplicate input connections (two connections to same input)
1787        let mut seen_inputs: HashMap<(u64, usize), u64> = HashMap::new();
1788        for conn in &self.connections {
1789            let key = (conn.to_node.0, conn.to_socket);
1790            if let Some(prev) = seen_inputs.insert(key, conn.from_node.0) {
1791                errors.push(format!(
1792                    "Duplicate connection to node {} socket {}: from {} and {}",
1793                    conn.to_node.0, conn.to_socket, prev, conn.from_node.0
1794                ));
1795            }
1796        }
1797        errors
1798    }
1799
1800    /// Compute a topology hash for caching/deduplication.
1801    pub fn topology_hash(&self) -> u64 {
1802        let mut hash: u64 = 0xcbf29ce484222325; // FNV offset
1803        let prime: u64 = 0x100000001b3;
1804
1805        // Sort node IDs for deterministic hashing
1806        let mut node_ids: Vec<u64> = self.nodes.keys().map(|k| k.0).collect();
1807        node_ids.sort();
1808
1809        for nid in &node_ids {
1810            hash ^= *nid;
1811            hash = hash.wrapping_mul(prime);
1812            if let Some(node) = self.nodes.get(&NodeId(*nid)) {
1813                // Hash node type via its display name
1814                for b in node.node_type.display_name().bytes() {
1815                    hash ^= b as u64;
1816                    hash = hash.wrapping_mul(prime);
1817                }
1818            }
1819        }
1820
1821        // Hash connections
1822        let mut conns: Vec<(u64, usize, u64, usize)> = self.connections.iter()
1823            .map(|c| (c.from_node.0, c.from_socket, c.to_node.0, c.to_socket))
1824            .collect();
1825        conns.sort();
1826        for (a, b, c, d) in conns {
1827            hash ^= a;
1828            hash = hash.wrapping_mul(prime);
1829            hash ^= b as u64;
1830            hash = hash.wrapping_mul(prime);
1831            hash ^= c;
1832            hash = hash.wrapping_mul(prime);
1833            hash ^= d as u64;
1834            hash = hash.wrapping_mul(prime);
1835        }
1836
1837        hash
1838    }
1839
1840    /// Return the internal nodes map (used for serialization).
1841    pub fn nodes_map(&self) -> &HashMap<NodeId, ShaderNode> {
1842        &self.nodes
1843    }
1844
1845    /// Return the next_id counter (used for serialization).
1846    pub fn next_id_counter(&self) -> u64 {
1847        self.next_id
1848    }
1849
1850    /// Set the next_id counter (used during deserialization).
1851    pub fn set_next_id(&mut self, val: u64) {
1852        self.next_id = val;
1853    }
1854
1855    /// Add a raw connection without duplicate-input removal (used during deserialization).
1856    pub fn add_connection_raw(&mut self, conn: Connection) {
1857        self.connections.push(conn);
1858    }
1859}
1860
1861impl Default for ShaderGraph {
1862    fn default() -> Self {
1863        Self::new("untitled")
1864    }
1865}