diff options
Diffstat (limited to 'src/sdf.rs')
-rw-r--r-- | src/sdf.rs | 206 |
1 files changed, 170 insertions, 36 deletions
@@ -7,76 +7,210 @@ macro_rules! write_str { ($( $arg:tt )*) => { write!($($arg)*).unwrap() } } -enum R3ToR3 { +/// these are constant across 3D space, not across time/user input/etc. +pub enum Constant { + F32(f32), + Time(f32, f32), +} + +impl From<f32> for Constant { + fn from(x: f32) -> Self { + Self::F32(x) + } +} + +impl Constant { + fn to_glsl(&self) -> String { + use Constant::*; + match self { + F32(x) => format!("{x:.1}"), + Time(x, y) => format!("({x:.1} * u_time + {y:.1})"), + } + } +} + +pub struct Constant3(Constant, Constant, Constant); + +impl Constant3 { + fn to_glsl(&self) -> String { + format!( + "vec3({}, {}, {})", + self.0.to_glsl(), + self.1.to_glsl(), + self.2.to_glsl() + ) + } +} + +pub enum R3ToR3 { Identity, - Translate([f32; 3]) + Translate(Constant3), } -enum RToR { +pub enum RToR { Identity, - Add(f32) + Add(Constant), +} + +pub enum R3ToR { + Sphere(Constant), + Cube(Constant), + PrePost(Box<R3ToR3>, Box<R3ToR>, Box<RToR>), + Mix(Box<R3ToR>, Box<R3ToR>, Constant), + SmoothMin(Box<R3ToR>, Box<R3ToR>), + Min(Box<R3ToR>, Box<R3ToR>), +} + +impl R3ToR { + pub fn sphere_f32(r: f32) -> Self { + Self::Sphere(r.into()) + } + + pub fn cube_f32(r: f32) -> Self { + Self::Cube(r.into()) + } + + pub fn mix(a: Self, b: Self, t: Constant) -> Self { + Self::Mix(Box::new(a), Box::new(b), t) + } + + pub fn mix_f32(a: Self, b: Self, t: f32) -> Self { + Self::mix(a, b, t.into()) + } +} + +struct VarCounter { + idx: u32, +} + +impl VarCounter { + fn new() -> Self { + Self { idx: 0 } + } + + fn prev(&self) -> u32 { + assert!(self.idx != 0); + self.idx - 1 + } + + fn next(&mut self) -> u32 { + let ret = self.idx; + self.idx += 1; + ret + } } pub struct Sdf { - pre: R3ToR3, - post: RToR + distance_function: R3ToR, +} + +trait Function { + /// treats `v<input>` as the input, and puts the output in `v<return value>`. + fn to_glsl(&self, input: u32, code: &mut String, var: &mut VarCounter) -> u32; } -impl RToR { - /// treats `v<*initial value of var_idx>` as the input, and puts the output in `v<final value of *var_idx>`. - fn to_glsl(&self, code: &mut String, var_idx: &mut u32) { +impl Function for RToR { + fn to_glsl(&self, input: u32, code: &mut String, var: &mut VarCounter) -> u32 { use RToR::*; - let input = *var_idx; - + match self { - Identity => {}, // no code + Identity => return input, Add(x) => { - *var_idx += 1; - let output = *var_idx; - write_str!(code, "float v{output} = v{input} + {x};\n"); + write_str!( + code, + "float v{} = v{input} + {};\n", + var.next(), + x.to_glsl() + ); } } + var.prev() } } -impl R3ToR3 { - /// treats `v<*initial value of var_idx>` as the input, and puts the output in `v<final value of *var_idx>`. - fn to_glsl(&self, code: &mut String, var_idx: &mut u32) { +impl Function for R3ToR3 { + fn to_glsl(&self, input: u32, code: &mut String, var: &mut VarCounter) -> u32 { use R3ToR3::*; - let input = *var_idx; - + + match self { + Identity => return input, + Translate(by) => { + write_str!( + code, + "vec3 v{} = v{input} + {};\n", + var.next(), + by.to_glsl() + ); + } + } + + var.prev() + } +} + +impl Function for R3ToR { + fn to_glsl(&self, input: u32, code: &mut String, var: &mut VarCounter) -> u32 { + use R3ToR::*; match self { - Identity => {}, // no code - Translate([x, y, z]) => { - *var_idx += 1; - let output = *var_idx; - write_str!(code, "vec3 v{output} = v{input} + vec3({x}, {y}, {z});\n"); - }, + // thanks to https://iquilezles.org/articles/distfunctions/ for + // these SDFs. + Sphere(r) => { + let r = r.to_glsl(); + write_str!(code, "float v{} = length(v{input}) - {r};\n", var.next()); + } + Cube(r) => { + let r = r.to_glsl(); + let q = var.next(); + write_str!(code, "vec3 v{q} = abs(v{input}) - {r};\n"); + write_str!( + code, + "float v{} = length(max(v{q},0.0)) + min(max(v{q}.x,max(v{q}.y,v{q}.z)),0.0);\n", + var.next() + ) + } + Mix(a, b, t) => { + let t = t.to_glsl(); + let a_output = a.to_glsl(input, code, var); + let b_output = b.to_glsl(input, code, var); + write_str!( + code, + "float v{} = mix(v{a_output}, v{b_output}, clamp({t}, 0.0, 1.0));\n", + var.next() + ); + } + _ => todo!(), } + + var.prev() } } impl Sdf { /// test sphere - pub fn sphere() -> Self { + pub fn sphere(r: f32) -> Self { Self { - pre: R3ToR3::Identity, - post: RToR::Identity + distance_function: R3ToR::Sphere(Constant::F32(r)), } } + pub fn from_function(distance_function: R3ToR) -> Self { + Self { distance_function } + } + /// appends some glsl code including a function `float sdf(vec3 p) { ... }` pub fn to_glsl(&self, code: &mut String) { code.push_str("float sdf(vec3 p) {\n"); // don't start out right next to the origin, since weird stuff might be happening there let origin_dist: f32 = 3.0; - write_str!(code, "vec3 v0 = p - vec3(0,0,-{}.);\n", origin_dist); - let mut var_idx = 0; - self.pre.to_glsl(code, &mut var_idx); - write_str!(code, "float v{} = length(v{}) - 1.0;\n", var_idx + 1, var_idx); - var_idx += 1; - self.post.to_glsl(code, &mut var_idx); - write_str!(code, "return v{var_idx};\n"); + let mut var = VarCounter::new(); + write_str!( + code, + "vec3 v{} = p - vec3(0,0,-{}.);\n", + var.next(), + origin_dist + ); + let output = self.distance_function.to_glsl(var.prev(), code, &mut var); + write_str!(code, "return v{output};\n"); code.push('}'); } } |