diff options
author | pommicket <pommicket@gmail.com> | 2022-12-13 17:06:43 -0500 |
---|---|---|
committer | pommicket <pommicket@gmail.com> | 2022-12-13 17:06:43 -0500 |
commit | 3f699f3720ef8d912e509bc3785485b527419dc0 (patch) | |
tree | e598c780453ca2b7b4fc39e1f971a18213930708 /src | |
parent | 2e4f8e26edf0a3c367c4e12ce8e80e8797149df3 (diff) |
sphere->cube!
Diffstat (limited to 'src')
-rw-r--r-- | src/main.rs | 144 | ||||
-rw-r--r-- | src/sdf.rs | 206 | ||||
-rw-r--r-- | src/win.rs | 92 |
3 files changed, 326 insertions, 116 deletions
diff --git a/src/main.rs b/src/main.rs index 2ef9ff4..6fef680 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,21 +1,23 @@ /* @TODO: +- fix rotation - fullscreen key - options for: - - max framerate - - AA quality - - # iterations, distance cutoff + - max framerate + - mouse sensitivity + - AA quality + - # iterations, distance cutoff */ extern crate nalgebra; +pub mod sdf; mod sdl; pub mod win; -pub mod sdf; -use nalgebra::{Vector3, Matrix3, Rotation3, Matrix4}; +use nalgebra::{Matrix3, Matrix4, Rotation3, Vector3}; use std::time::Instant; - + type Vec3 = Vector3<f32>; type Mat3 = Matrix3<f32>; type Mat4 = Matrix4<f32>; @@ -24,7 +26,7 @@ type Rot3 = Rotation3<f32>; struct View { pos: Vec3, yaw: f32, - pitch: f32 + pitch: f32, } impl Default for View { @@ -32,7 +34,7 @@ impl Default for View { Self { pos: Vec3::zeros(), yaw: 0.0, - pitch: 0.0 + pitch: 0.0, } } } @@ -42,35 +44,37 @@ impl View { fn rotation(&self) -> Mat3 { *Rot3::from_euler_angles(self.pitch, self.yaw, 0.0).matrix() } - + fn translation(&self) -> Mat4 { Mat4::new_translation(&self.pos) } - + fn transform(&self) -> Mat4 { self.translation() * self.rotation().to_homogeneous() } - - fn inv_transform(&self) -> Mat4 { - // this matrix should always be invertible - self.transform().try_inverse().unwrap() - } -} +} fn try_main() -> Result<(), String> { - let my_sdf = sdf::Sdf::sphere(); + use sdf::{R3ToR, Constant}; + let funciton = R3ToR::mix(R3ToR::sphere_f32(1.0), R3ToR::cube_f32(1.0), + Constant::Time(0.1, 0.0)); + let my_sdf = sdf::Sdf::from_function(funciton); let mut window = win::Window::new("AutoSDF", 1280, 720, true) .map_err(|e| format!("Error creating window: {e}"))?; - + let mut fshader_source = String::new(); - fshader_source.push_str(" + fshader_source.push_str( + " IN vec2 pos; uniform mat4 u_transform; -"); +uniform float u_time; +", + ); my_sdf.to_glsl(&mut fshader_source); - fshader_source.push_str(" + fshader_source.push_str( + " #define ITERATIONS 20 #define AA_X 1 #define AA_Y 1 @@ -117,11 +121,15 @@ void main() { for (int m = 0; m < AA_X; m++) { for (int n = 0; n < AA_Y; n++) { vec2 aa_offset = vec2(float(m), float(n)) * aa_delta; - vec3 absolute_pos = vec3(pos + aa_offset, -focal_length); - vec3 delta = normalize(absolute_pos); + vec3 p = (u_transform * vec4(pos + aa_offset, -focal_length, 1.0)).xyz; + vec3 delta = normalize(p); + if (sdf(p) < 0.0) { + // looking inside object + o_color = vec4(1.0, 0.0, 1.0, 1.0); + return; + } int i; for (i = 0; i < ITERATIONS; i++) { - vec3 p = (u_transform * vec4(absolute_pos, 1.0)).xyz; float dist = sdf(p); min_dist = min(min_dist, dist); if (dist <= 0.01) { @@ -130,18 +138,20 @@ void main() { break; } if (dist > 100.0) break;//little optimization - absolute_pos += dist * delta; + p += dist * delta; } } } final_color *= 1.0 / (AA_X * AA_Y); o_color = vec4(final_color, 1.0); -}"); - +}", + ); + println!("{fshader_source}"); - - let program = window.create_program( - "attribute vec2 v_pos; + + let program = window + .create_program( + "attribute vec2 v_pos; OUT vec2 pos; uniform float u_aspect_ratio; @@ -149,9 +159,10 @@ void main() { pos = v_pos * vec2(u_aspect_ratio, 1.0); gl_Position = vec4(v_pos, 0.0, 1.0); }", - &fshader_source - ).map_err(|e| format!("Error compiling shader:\n{e}"))?; - + &fshader_source, + ) + .map_err(|e| format!("Error compiling shader:\n{e}"))?; + let mut buffer = window.create_buffer(); let data: &[[f32; 2]] = &[ [-1.0, -1.0], @@ -164,15 +175,19 @@ void main() { window.set_buffer_data(&mut buffer, data); let mut array = window.create_vertex_array(buffer, &program); window.array_attrib2f(&mut array, "v_pos", 0); - + let mut view = View::default(); - + window.set_mouse_relative(true); - + let mut frame_time = Instant::now(); let mut show_debug_info = false; - + let mut total_time = 0.0; + 'mainloop: loop { + let frame_dt = frame_time.elapsed().as_secs_f32(); + frame_time = Instant::now(); + while let Some(event) = window.next_event() { use win::Event::*; use win::Key::*; @@ -180,29 +195,62 @@ void main() { Quit | KeyDown(Escape) => break 'mainloop, KeyDown(F1) => show_debug_info = !show_debug_info, MouseMotion { xrel, yrel, .. } => { - view.yaw += xrel as f32 * 0.01; - view.pitch += yrel as f32 * 0.01; - }, - _ => {}, + view.yaw -= xrel as f32 * frame_dt; + view.pitch -= yrel as f32 * frame_dt; + } + _ => {} } } - window.viewport_full_screen(); + { // movement + let mut dx = 0.0; + let mut dy = 0.0; + let mut dz = 0.0; + use win::Key::{Left, Right, Up, Down, W, A, S, D, Q, E}; + if window.is_key_down(W) || window.is_key_down(Up) { + dz -= 1.0; + } + if window.is_key_down(S) || window.is_key_down(Down) { + dz += 1.0; + } + if window.is_key_down(A) || window.is_key_down(Left) { + dx -= 1.0; + } + if window.is_key_down(D) || window.is_key_down(Right) { + dx += 1.0; + } + if window.is_key_down(Q) { + dy += 1.0; + } + if window.is_key_down(E) { + dy -= 1.0; + } + let motion = Vec3::new(dx, dy, dz); + if let Some(motion) = motion.try_normalize(0.001) { + let motion = motion * frame_dt; + let motion = view.rotation() * motion; + view.pos += motion; + } + } + window.viewport_full_screen(); + window.clear_screen(win::ColorF32::BLACK); window.use_program(&program); window.uniform1f("u_aspect_ratio", window.aspect_ratio()); - window.uniform4x4f("u_transform", view.inv_transform().as_slice()); - + window.uniform1f("u_time", total_time); + window.uniform4x4f("u_transform", view.transform().as_slice()); + window.draw_array(&array); - + window.swap(); if show_debug_info { - println!("frame time = {:?}",frame_time.elapsed()); - frame_time = Instant::now(); + println!("frame time = {:?}ms", frame_dt * 1000.0); } + + total_time += frame_dt; } - + Ok(()) } @@ -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('}'); } } @@ -2,11 +2,11 @@ // this is because OpenGL is not thread safe. use crate::sdl; use gl::types::{GLchar, GLenum, GLint, GLsizei, GLuint}; +use mem::size_of; #[allow(unused_imports)] use std::ffi::{c_char, c_int, c_uint, c_void, CStr, CString}; use std::sync::Mutex; use std::{fmt, mem}; -use mem::size_of; pub type AudioCallback = fn(sample_rate: u32, samples: &mut [f32]); @@ -79,7 +79,7 @@ pub enum Key { F9, F10, F11, - F12 + F12, } impl Key { @@ -212,7 +212,12 @@ pub enum Event { Quit, KeyDown(Key), KeyUp(Key), - MouseMotion { x: i32, y: i32, xrel: i32, yrel: i32 }, + MouseMotion { + x: i32, + y: i32, + xrel: i32, + yrel: i32, + }, } pub fn display_error_message(message: &str) { @@ -308,11 +313,11 @@ impl From<u32> for ColorU8 { impl ColorF32 { pub const BLACK: Self = Self::rgb(0.0, 0.0, 0.0); - + pub const fn rgb(r: f32, g: f32, b: f32) -> Self { ColorF32 { r, g, b, a: 1.0 } } - + pub const fn rgba(r: f32, g: f32, b: f32, a: f32) -> Self { ColorF32 { r, g, b, a } } @@ -342,25 +347,27 @@ impl Shader { { //set source // @TODO(eventually): support for older versions of GLSL - let header = if r#type == gl::FRAGMENT_SHADER { "#version 130 + let header = if r#type == gl::FRAGMENT_SHADER { + "#version 130 #define IN in #define OUT out #define gl_FragColor o_color out vec4 o_color; #line 1 -" } else { -"#version 130 +" + } else { + "#version 130 #define IN in #define OUT out #define ATTRIBUTE in #line 1 " -}; + }; let hdrptr = header.as_bytes().as_ptr() as *const GLchar; let srcptr = source.as_bytes().as_ptr() as *const GLchar; let sources = [hdrptr, srcptr]; let lengths = [header.len() as GLint, source.len() as GLint]; - + let sources_ptr = &sources[0] as *const *const GLchar; let lengths_ptr = &lengths[0] as *const GLint; @@ -427,7 +434,7 @@ impl Program { eprintln!("{}", String::from_utf8_lossy(&log[..len as usize])); } } - + { let mut status: GLint = 0; unsafe { gl::GetProgramiv(id, gl::LINK_STATUS, (&mut status) as _) }; @@ -435,7 +442,7 @@ impl Program { return Err(format!("failed to link")); } } - + Ok(Self { id }) } } @@ -503,7 +510,11 @@ impl VertexArray { unsafe { gl::GenVertexArrays(1, &mut id as *mut GLuint) }; - Self { id, buffer, program: program.id } + Self { + id, + buffer, + program: program.id, + } } fn bind(&self) { @@ -515,16 +526,23 @@ impl VertexArray { let cstr = cstring.as_ptr() as *const GLchar; let loc = unsafe { gl::GetAttribLocation(self.program, cstr) }; let Ok(loc) = loc.try_into() else { return false }; - + if offset + usize::from(n) * size_of::<f32>() > self.buffer.stride as usize { // offset too large return false; } - + self.bind(); self.buffer.bind(); unsafe { - gl::VertexAttribPointer(loc, n.into(), gl::FLOAT, 0, self.buffer.stride as _, offset as _) + gl::VertexAttribPointer( + loc, + n.into(), + gl::FLOAT, + 0, + self.buffer.stride as _, + offset as _, + ) }; unsafe { gl::EnableVertexAttribArray(loc) }; true @@ -662,9 +680,11 @@ impl Window { pub fn show(&mut self) { unsafe { sdl::show_window(self.sdlwin) }; } - + pub fn set_mouse_relative(&mut self, relative: bool) { - unsafe { sdl::set_relative_mouse_mode(relative); } + unsafe { + sdl::set_relative_mouse_mode(relative); + } } pub fn create_program( @@ -689,18 +709,24 @@ impl Window { VertexArray::new(buffer, program) } - fn array_attribnf(&mut self, array: &mut VertexArray, n: u8, name: &str, offset: usize) -> bool { + fn array_attribnf( + &mut self, + array: &mut VertexArray, + n: u8, + name: &str, + offset: usize, + ) -> bool { array.attribnf(n, name, offset) } - + pub fn array_attrib2f(&mut self, array: &mut VertexArray, name: &str, offset: usize) -> bool { self.array_attribnf(array, 2, name, offset) } - + pub fn array_attrib3f(&mut self, array: &mut VertexArray, name: &str, offset: usize) -> bool { self.array_attribnf(array, 3, name, offset) } - + pub fn array_attrib4f(&mut self, array: &mut VertexArray, name: &str, offset: usize) -> bool { self.array_attribnf(array, 4, name, offset) } @@ -711,7 +737,7 @@ impl Window { unsafe { sdl::get_window_size(self.sdlwin, &mut x, &mut y) }; (x, y) } - + pub fn aspect_ratio(&self) -> f32 { let (w, h) = self.size(); return w as f32 / h as f32; @@ -720,7 +746,7 @@ impl Window { pub fn viewport(&mut self, x: i32, y: i32, w: i32, h: i32) { unsafe { gl::Viewport(x, y, w, h) }; } - + pub fn viewport_full_screen(&mut self) { let (w, h) = self.size(); self.viewport(0, 0, w, h); @@ -741,16 +767,16 @@ impl Window { return Some(Event::KeyUp(k)); } } - }, + } sdl::SDL_MOUSEMOTION => { let motion = unsafe { sdl.motion }; return Some(Event::MouseMotion { x: motion.x, y: motion.y, xrel: motion.xrel, - yrel: motion.yrel + yrel: motion.yrel, }); - }, + } _ => {} } } @@ -768,7 +794,10 @@ impl Window { unsafe { gl::GenTextures(1, (&mut id) as *mut GLuint); } - Texture { id, params: params.clone() } + Texture { + id, + params: params.clone(), + } } pub fn set_texture_data( @@ -869,7 +898,7 @@ impl Window { self.used_program = program.id; } } - + fn get_uniform_location(&self, name: &str) -> Option<GLint> { if self.used_program == 0 { return None; @@ -904,7 +933,7 @@ impl Window { gl::Uniform2i(loc, x, y); } } - + pub fn uniform3i(&mut self, name: &str, x: i32, y: i32, z: i32) { let loc = self.get_uniform_location(name).unwrap_or(-1); unsafe { @@ -932,7 +961,7 @@ impl Window { gl::Uniform2f(loc, x, y); } } - + pub fn uniform3f(&mut self, name: &str, x: f32, y: f32, z: f32) { let loc = self.get_uniform_location(name).unwrap_or(-1); unsafe { @@ -963,7 +992,6 @@ impl Window { } } - pub fn uniform_texture(&mut self, name: &str, slot: u32) { self.uniform1i(name, slot as i32); } |