use cord_trig::{NodeId, TrigGraph, TrigOp}; use std::fmt::Write; /// Generate a complete WGSL raymarcher directly from a TrigGraph. pub fn generate_wgsl_from_trig(graph: &TrigGraph) -> String { let mut out = String::with_capacity(4096); write_preamble(&mut out); write_sdf_from_trig(&mut out, graph); write_raymarcher(&mut out); out } fn var_name(id: NodeId) -> String { format!("v{id}") } fn write_sdf_from_trig(out: &mut String, graph: &TrigGraph) { out.push_str("fn scene_sdf(p: vec3) -> f32 {\n"); for (i, op) in graph.nodes.iter().enumerate() { let v = var_name(i as NodeId); match op { TrigOp::InputX => writeln!(out, " let {v} = p.x;").unwrap(), TrigOp::InputY => writeln!(out, " let {v} = p.y;").unwrap(), TrigOp::InputZ => writeln!(out, " let {v} = p.z;").unwrap(), TrigOp::Const(c) => { let f = *c as f32; if f.is_nan() || f.is_infinite() { writeln!(out, " let {v} = 0.0;").unwrap() } else { writeln!(out, " let {v} = {f:.8};").unwrap() } } TrigOp::Add(a, b) => writeln!(out, " let {v} = {} + {};", var_name(*a), var_name(*b)).unwrap(), TrigOp::Sub(a, b) => writeln!(out, " let {v} = {} - {};", var_name(*a), var_name(*b)).unwrap(), TrigOp::Mul(a, b) => writeln!(out, " let {v} = {} * {};", var_name(*a), var_name(*b)).unwrap(), TrigOp::Div(a, b) => writeln!(out, " let {v} = {} / {};", var_name(*a), var_name(*b)).unwrap(), TrigOp::Neg(a) => writeln!(out, " let {v} = -{};", var_name(*a)).unwrap(), TrigOp::Abs(a) => writeln!(out, " let {v} = abs({});", var_name(*a)).unwrap(), TrigOp::Sin(a) => writeln!(out, " let {v} = sin({});", var_name(*a)).unwrap(), TrigOp::Cos(a) => writeln!(out, " let {v} = cos({});", var_name(*a)).unwrap(), TrigOp::Tan(a) => writeln!(out, " let {v} = tan({});", var_name(*a)).unwrap(), TrigOp::Asin(a) => writeln!(out, " let {v} = asin({});", var_name(*a)).unwrap(), TrigOp::Acos(a) => writeln!(out, " let {v} = acos({});", var_name(*a)).unwrap(), TrigOp::Atan(a) => writeln!(out, " let {v} = atan({});", var_name(*a)).unwrap(), TrigOp::Sinh(a) => writeln!(out, " let {v} = sinh({});", var_name(*a)).unwrap(), TrigOp::Cosh(a) => writeln!(out, " let {v} = cosh({});", var_name(*a)).unwrap(), TrigOp::Tanh(a) => writeln!(out, " let {v} = tanh({});", var_name(*a)).unwrap(), TrigOp::Asinh(a) => writeln!(out, " let {v} = asinh({});", var_name(*a)).unwrap(), TrigOp::Acosh(a) => writeln!(out, " let {v} = acosh({});", var_name(*a)).unwrap(), TrigOp::Atanh(a) => writeln!(out, " let {v} = atanh({});", var_name(*a)).unwrap(), TrigOp::Sqrt(a) => writeln!(out, " let {v} = sqrt({});", var_name(*a)).unwrap(), TrigOp::Exp(a) => writeln!(out, " let {v} = exp({});", var_name(*a)).unwrap(), TrigOp::Ln(a) => writeln!(out, " let {v} = log({});", var_name(*a)).unwrap(), TrigOp::Hypot(a, b) => { writeln!(out, " let {v} = sqrt({a_v} * {a_v} + {b_v} * {b_v});", a_v = var_name(*a), b_v = var_name(*b)).unwrap() } TrigOp::Atan2(a, b) => { writeln!(out, " let {v} = atan2({}, {});", var_name(*a), var_name(*b)).unwrap() } TrigOp::Min(a, b) => writeln!(out, " let {v} = min({}, {});", var_name(*a), var_name(*b)).unwrap(), TrigOp::Max(a, b) => writeln!(out, " let {v} = max({}, {});", var_name(*a), var_name(*b)).unwrap(), TrigOp::Clamp { val, lo, hi } => { writeln!(out, " let {v} = clamp({}, {}, {});", var_name(*val), var_name(*lo), var_name(*hi)).unwrap() } } } writeln!(out, " return {};", var_name(graph.output)).unwrap(); out.push_str("}\n\n"); } fn write_preamble(out: &mut String) { out.push_str( r#"struct Uniforms { resolution: vec2, viewport_offset: vec2, camera_pos: vec3, time: f32, camera_target: vec3, fov: f32, render_flags: vec4, scene_scale: f32, _pad: vec3, }; @group(0) @binding(0) var u: Uniforms; "#); } fn write_raymarcher(out: &mut String) { out.push_str( r#"fn calc_normal(p: vec3) -> vec3 { let e = vec2(0.0005 * u.scene_scale, -0.0005 * u.scene_scale); return normalize( e.xyy * scene_sdf(p + e.xyy) + e.yyx * scene_sdf(p + e.yyx) + e.yxy * scene_sdf(p + e.yxy) + e.xxx * scene_sdf(p + e.xxx) ); } fn soft_shadow(ro: vec3, rd: vec3, mint: f32, maxt: f32, k: f32) -> f32 { let eps = 0.0002 * u.scene_scale; let step_lo = 0.001 * u.scene_scale; let step_hi = 0.5 * u.scene_scale; var res = 1.0; var t = mint; var prev_d = 1e10; for (var i = 0; i < 64; i++) { let d = scene_sdf(ro + rd * t); if d < eps { return 0.0; } let y = d * d / (2.0 * prev_d); let x = sqrt(max(d * d - y * y, 0.0)); res = min(res, k * x / max(t - y, 0.0001)); prev_d = d; t += clamp(d, step_lo, step_hi); if t > maxt { break; } } return clamp(res, 0.0, 1.0); } fn ao(p: vec3, n: vec3) -> f32 { let s = u.scene_scale; var occ = 0.0; var w = 1.0; for (var i = 0; i < 5; i++) { let h = (0.01 + 0.12 * f32(i)) * s; let d = scene_sdf(p + n * h); occ += (h - d) * w; w *= 0.7; } return clamp(1.0 - 1.5 * occ / s, 0.0, 1.0); } fn grid_aa(x: f32, line_w: f32) -> f32 { let d = abs(fract(x) - 0.5); let fw = fwidth(x); return smoothstep(0.0, max(fw * 1.5, 0.001), d - line_w); } fn ground_plane(ro: vec3, rd: vec3) -> vec4 { if rd.z >= 0.0 { return vec4(0.0); } let t = -ro.z / rd.z; let max_ground = u.scene_scale * 50.0; if t < 0.0 || t > max_ground { return vec4(0.0); } let p = ro + rd * t; let gs = max(u.scene_scale * 0.5, 1.0); let gp = p.xy / gs; // Minor grid (every cell) let minor = grid_aa(gp.x, 0.02) * grid_aa(gp.y, 0.02); // Major grid (every 5 cells) let major = grid_aa(gp.x / 5.0, 0.04) * grid_aa(gp.y / 5.0, 0.04); // Axis lines at world origin let aw = gs * 0.08; let afw_x = fwidth(p.x); let afw_y = fwidth(p.y); let ax = 1.0 - smoothstep(aw, aw + max(afw_y * 1.5, 0.001), abs(p.y)); let ay = 1.0 - smoothstep(aw, aw + max(afw_x * 1.5, 0.001), abs(p.x)); let fade_k = 0.3 / (u.scene_scale * u.scene_scale); let fade = exp(-fade_k * t * t); let base = vec3(0.22, 0.24, 0.28); var col = mix(vec3(0.30, 0.32, 0.36), base, minor); col = mix(vec3(0.38, 0.40, 0.44), col, major); col = mix(vec3(0.55, 0.18, 0.18), col, ax * fade); col = mix(vec3(0.18, 0.45, 0.18), col, ay * fade); let sky_horizon = vec3(0.25, 0.35, 0.50); col = mix(sky_horizon, col, fade); let shad_max = u.scene_scale * 10.0; let shad = soft_shadow(vec3(p.x, p.y, 0.001 * u.scene_scale), normalize(vec3(0.5, 0.8, 1.0)), 0.1 * u.scene_scale, shad_max, 8.0); let shad_faded = mix(1.0, 0.5 + 0.5 * shad, fade); return vec4(col * shad_faded, t); } fn get_camera_ray(uv: vec2) -> vec3 { let forward = normalize(u.camera_target - u.camera_pos); let right = normalize(cross(forward, vec3(0.0, 0.0, 1.0))); let up = cross(right, forward); return normalize(forward * u.fov + right * uv.x - up * uv.y); } fn shade_ray(ro: vec3, rd: vec3) -> vec3 { let hit_eps = 0.0005 * u.scene_scale; let max_t = u.scene_scale * 20.0; var t = 0.0; var min_d = 1e10; var t_min = 0.0; var hit = false; for (var i = 0; i < 128; i++) { let p = ro + rd * t; let d = scene_sdf(p); if d < min_d { min_d = d; t_min = t; } if d < hit_eps { hit = true; break; } t += d; if t > max_t { break; } } var bg = vec3(0.0); let gp = ground_plane(ro, rd); if gp.w > 0.0 && u.render_flags.z > 0.5 { bg = gp.xyz; } else { bg = mix(vec3(0.25, 0.35, 0.50), vec3(0.05, 0.12, 0.35), clamp(rd.z * 1.5, 0.0, 1.0)); } bg = pow(bg, vec3(0.4545)); // SDF-derived coverage — analytical AA from the distance field let pix = max(t_min, 0.001) / u.resolution.y; var coverage: f32; if hit { coverage = 1.0; } else { coverage = 1.0 - smoothstep(0.0, pix * 2.0, min_d); } if coverage < 0.005 { return bg; } let shade_t = select(t_min, t, hit); let p = ro + rd * shade_t; let n = calc_normal(p); let light_dir = normalize(vec3(0.5, 0.8, 1.0)); let diff = max(dot(n, light_dir), 0.0); let shad = mix(1.0, soft_shadow(p + n * 0.002 * u.scene_scale, light_dir, 0.02 * u.scene_scale, 15.0 * u.scene_scale, 8.0), u.render_flags.x); let occ = mix(1.0, ao(p, n), u.render_flags.y); let half_v = normalize(light_dir - rd); let spec = pow(max(dot(n, half_v), 0.0), 48.0) * 0.5; let sky_light = clamp(0.5 + 0.5 * n.z, 0.0, 1.0); let bounce = clamp(0.5 - 0.5 * n.z, 0.0, 1.0); let base = vec3(0.65, 0.67, 0.72); var lin = vec3(0.0); lin += diff * shad * vec3(1.0, 0.97, 0.9) * 1.5; lin += sky_light * occ * vec3(0.30, 0.40, 0.60) * 0.7; lin += bounce * occ * vec3(0.15, 0.12, 0.1) * 0.5; var color = base * lin + spec * shad; color = color / (color + vec3(1.0)); color = pow(color, vec3(0.4545)); return mix(bg, color, coverage); } @fragment fn fs_main(@builtin(position) frag_coord: vec4) -> @location(0) vec4 { let ro = u.camera_pos; let px = 1.0 / u.resolution.y; let uv = (frag_coord.xy - u.viewport_offset - u.resolution * 0.5) * px; let rd = get_camera_ray(uv); return vec4(shade_ray(ro, rd), 1.0); } struct VsOutput { @builtin(position) position: vec4, }; @vertex fn vs_main(@builtin(vertex_index) idx: u32) -> VsOutput { var pos = array, 3>( vec2(-1.0, -1.0), vec2(3.0, -1.0), vec2(-1.0, 3.0), ); var out: VsOutput; out.position = vec4(pos[idx], 0.0, 1.0); return out; } "#); }