diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/fshader_main.glsl | 55 | ||||
-rw-r--r-- | src/main.rs | 43 | ||||
-rw-r--r-- | src/sdf.rs | 2 | ||||
-rw-r--r-- | src/sdl.rs | 75 | ||||
-rw-r--r-- | src/win.rs | 14 |
5 files changed, 137 insertions, 52 deletions
diff --git a/src/fshader_main.glsl b/src/fshader_main.glsl index aa43b27..9e9334e 100644 --- a/src/fshader_main.glsl +++ b/src/fshader_main.glsl @@ -11,6 +11,14 @@ uniform float u_distance_threshold; uniform int u_hsv; uniform ivec2 u_antialiasing; uniform int u_iterations; +uniform float u_aspect_ratio; +uniform vec4 u_flash; +uniform int u_flash_icon; + +#define ICON_COPY 1 +#define ICON_PLAY 2 +#define ICON_PAUSE 3 +#define ICON_REWIND 4 %COMMON% %SDF% @@ -52,14 +60,21 @@ float sdf_adjusted(vec3 p) { vec3 normal(vec3 p) { -// thanks to https://iquilezles.org/articles/normalsSDF/ - float h = 0.0001; - vec2 k = vec2(1.,-1.); - vec3 sdf_normal = k.xyy*sdf(p + k.xyy*h) + - k.yyx*sdf(p + k.yyx*h) + - k.yxy*sdf(p + k.yxy*h) + - k.xxx*sdf(p + k.xxx*h); - return normalize(sdf_normal); + // thanks to https://iquilezles.org/articles/normalsSDF/ + float h = 0.0001; + vec2 k = vec2(1.,-1.); + vec3 sdf_normal = k.xyy*sdf(p + k.xyy*h) + + k.yyx*sdf(p + k.yyx*h) + + k.yxy*sdf(p + k.yxy*h) + + k.xxx*sdf(p + k.xxx*h); + return normalize(sdf_normal); +} + +bool play_icon(vec2 pos) { + vec2 a = abs(pos); + if (a.x >= 0.5 || a.y >= 0.5) + return false; + return a.y < 0.25 + 0.5 * pos.x; } void main() { @@ -76,8 +91,8 @@ void main() { p += u_translation; if (sdf(p) < 0.0) { // looking inside object - gl_FragColor = vec4(get_color(p), 1.0); - return; + final_color += get_color(p); + continue; } int i; for (i = 0; i < ITERATIONS; i++) { @@ -112,5 +127,25 @@ void main() { } } final_color *= 1.0 / (AA_X * AA_Y); + bool icon = false; + switch (u_flash_icon) { + case 0: break; + case ICON_COPY: + icon = abs(pos.x) > u_aspect_ratio - 0.1 || abs(pos.y) > 0.9; + break; + case ICON_PLAY: + icon = play_icon(pos); + break; + case ICON_REWIND: + icon = play_icon(vec2(-pos.x, pos.y)); + break; + case ICON_PAUSE: + vec2 p = abs(pos); + icon = p.x >= 0.1 && p.x <= 0.4 && p.y <= 0.5; + break; + } + if (icon) + final_color = mix(final_color, u_flash.xyz, u_flash.w); + gl_FragColor = vec4(final_color, 1.0); } diff --git a/src/main.rs b/src/main.rs index ebf52fe..38898a2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,12 +1,9 @@ /* @TODO: -- publish git repo -- replace *mut SDL_Window with Window - options for: - max framerate - come up with twisty lipschitz continuous function, & add it -- feedback for copy/paste (flash screen or something) -- feedback for pause/unpause/rewind (flash icons) +- (slightly) more interesting constants - Params instead of depth for GenRandom - allow multiple endpoints (cube & sphere & ...) - let user go back&forth through past sdfs using scenes.txt file @@ -59,13 +56,23 @@ use std::{ io::{prelude::*, BufReader}, time::Instant, }; -use win::ColorGrayscaleF32; +use win::{ColorGrayscaleF32, ColorF32}; type Vec3 = Vector3<f32>; type Mat3 = Matrix3<f32>; type Mat4 = Matrix4<f32>; type Rot3 = Rotation3<f32>; +#[repr(i32)] +#[derive(Clone, Copy)] +enum Icon { + None = 0, + Copy = 1, + Play = 2, + Pause = 3, + Rewind = 4 +} + #[derive(Clone)] struct View { pos: Vec3, @@ -265,6 +272,9 @@ struct State { framebuffer: win::Framebuffer, main_array: win::VertexArray, test_array: win::VertexArray, + // displayed on top of the screen. used for feedback when copying/pasting/etc + flash: ColorF32, + flash_icon: Icon, } impl State { @@ -336,6 +346,8 @@ impl State { test_array, scene_list, settings, + flash: ColorF32::rgba(0.0, 0.0, 0.0, 0.0), + flash_icon: Icon::None, }; me.load_scene(scene); Ok(me) @@ -395,6 +407,15 @@ impl State { }; } + fn flash(&mut self, icon: Icon) { + self.flash = match icon { + Icon::None => ColorF32::BLACK, + Icon::Copy => ColorF32::GREEN, + _ => ColorF32::rgb(1.0,0.5,0.0), + }; + self.flash_icon = icon; + } + // returns false if we should quit fn frame(&mut self) -> bool { let frame_dt = self.frame_time.elapsed().as_secs_f32(); @@ -420,6 +441,7 @@ impl State { eprintln!("couldn't copy text to clipboard: {e}") } } + self.flash(Icon::Copy); } KeyDown { key: F, .. } => { self.fullscreen = !self.fullscreen; @@ -451,10 +473,13 @@ impl State { } => { if !self.view.paused() { self.view.pause(); + self.flash(Icon::Pause); } else if modifier.shift() { self.view.unpause(true); + self.flash(Icon::Play); } else { self.view.unpause(false); + self.flash(Icon::Rewind); } } MouseMotion { xrel, yrel, .. } => { @@ -564,6 +589,14 @@ impl State { ); window.uniform3x3f("u_rotation", view.rotation().as_slice()); window.uniform3f_slice("u_translation", view.pos.as_slice()); + window.uniform4f_color("u_flash", self.flash); + window.uniform1i("u_flash_icon", self.flash_icon as i32); + + self.flash.a = f32::max(self.flash.a - frame_dt * (2.0 - 1.0 * self.flash.a), 0.0); + if self.flash.a <= 0.0 { + // icon is no longer visible + self.flash_icon = Icon::None; + } window.draw_array(&self.main_array); @@ -18,7 +18,7 @@ macro_rules! write_str { /// these are constant across 3D space, not across time/user input/etc. #[derive(Debug, GenRandom, Serialize, Deserialize)] pub enum Constant { - #[prob(0.5)] + #[prob(0.0)] F32(f32), #[prob(0.5)] Time( @@ -786,33 +786,6 @@ impl SDL_AudioSpec { } } -pub struct Surface { - ptr: *mut SDL_Surface -} - -impl Surface { - /// Returns `None` if `ptr` is null. - /// # Safety - /// You may only call this function if `ptr` refers to a valid `SDL_Surface` - /// which can be freed with `SDL_FreeSurface`. - /// Make sure you only create one `Surface` for any particular surface pointer. - /// When the `Surface` is dropped, the `SDL_Surface` pointer will be freed. - pub unsafe fn from_raw(ptr: *mut SDL_Surface) -> Option<Self> { - if ptr.is_null() { - None - } else { - Some(Self { ptr }) - } - } -} - -impl Drop for Surface { - fn drop(&mut self) { - // SAFETY: this should only be constructed with a valid SDL surface pointer, - // and the pointer should never be freed by anything else. - unsafe { SDL_FreeSurface(self.ptr) }; - } -} #[link(name = "SDL2", kind = "dylib")] extern "C" { @@ -1122,8 +1095,38 @@ pub mod scancode { pub const NUM_SCANCODES: SDL_Scancode = 512; } -fn cstring(s: &str) -> Result<CString, String> { - CString::new(s).map_err(|e| format!("{e}")) +pub struct Surface { + ptr: *mut SDL_Surface +} + +impl Surface { + /// Returns `None` if `ptr` is null. + /// # Safety + /// You may only call this function if `ptr` refers to a valid `SDL_Surface` + /// which can be freed with `SDL_FreeSurface`. + /// Make sure you only create one `Surface` for any particular surface pointer. + /// When the `Surface` is dropped, the `SDL_Surface` pointer will be freed. + pub unsafe fn from_raw(ptr: *mut SDL_Surface) -> Option<Self> { + if ptr.is_null() { + None + } else { + Some(Self { ptr }) + } + } + + /// # Safety + /// It is your responsibility to use the pointer *before* dropping this `Surface`. + pub unsafe fn get_raw(&self) -> *mut SDL_Surface { + self.ptr + } +} + +impl Drop for Surface { + fn drop(&mut self) { + // SAFETY: this should only be constructed with a valid SDL surface pointer, + // and the pointer should never be freed by anything else. + unsafe { SDL_FreeSurface(self.ptr) }; + } } unsafe fn get_err() -> String { @@ -1141,7 +1144,9 @@ pub unsafe fn create_window( height: i32, flags: u32, ) -> Result<*mut SDL_Window, String> { - let tstr = cstring(title)?; + let Ok(tstr) = CString::new(title) else { + return Err("window title cannot contain null bytes".to_string()) + }; let window = SDL_CreateWindow( tstr.as_ptr(), SDL_WINDOWPOS_UNDEFINED, @@ -1193,8 +1198,8 @@ pub unsafe fn init() -> Result<(), String> { pub unsafe fn set_hint(hint: &str, value: &str) { // NOTE: hint,value are probably string literals, so cstring is very unlikely to fail - if let Ok(hstr) = cstring(hint) { - if let Ok(vstr) = cstring(value) { + if let Ok(hstr) = CString::new(hint) { + if let Ok(vstr) = CString::new(value) { SDL_SetHint(hstr.as_ptr(), vstr.as_ptr()); } } @@ -1210,7 +1215,7 @@ pub unsafe fn gl_set_context_version(major: i32, minor: i32) { } pub unsafe fn gl_get_proc_address(name: &str) -> *const std::os::raw::c_void { - match cstring(name) { + match CString::new(name) { Ok(cstr) => SDL_GL_GetProcAddress(cstr.as_ptr()), Err(_) => 0 as _, } @@ -1246,8 +1251,8 @@ pub unsafe fn show_simple_message_box( message: &str, window: *mut SDL_Window, ) -> Result<(), String> { - let tstr = cstring(title)?; - let mstr = cstring(message)?; + let tstr = CString::new(title).unwrap_or_else(|_| CString::new("bad title").unwrap()); + let mstr = CString::new(message).unwrap_or_else(|_| CString::new("bad message").unwrap()); SDL_ShowSimpleMessageBox(flags, tstr.as_ptr(), mstr.as_ptr(), window); Ok(()) } @@ -560,7 +560,11 @@ impl From<u32> for ColorU8 { } impl ColorF32 { - pub const BLACK: Self = Self::rgb(0.0, 0.0, 0.0); + pub const BLACK: Self = Self::gray(0.0); + pub const WHITE: Self = Self::gray(1.0); + pub const RED: Self = Self::rgb(1.0, 0.0, 0.0); + pub const GREEN: Self = Self::rgb(0.0, 1.0, 0.0); + pub const BLUE: Self = Self::rgb(0.0, 0.0, 1.0); pub const fn rgb(r: f32, g: f32, b: f32) -> Self { ColorF32 { r, g, b, a: 1.0 } @@ -569,6 +573,10 @@ impl ColorF32 { pub const fn rgba(r: f32, g: f32, b: f32, a: f32) -> Self { ColorF32 { r, g, b, a } } + + pub const fn gray(value: f32) -> Self { + Self::rgb(value, value, value) + } } unsafe impl Color for ColorF32 { @@ -1534,6 +1542,10 @@ impl Window { assert_eq!(xyzw.len(), 4); self.uniform4f(name, xyzw[0], xyzw[1], xyzw[2], xyzw[3]) } + + pub fn uniform4f_color(&mut self, name: &str, color: ColorF32) { + self.uniform4f(name, color.r, color.g, color.b, color.a); + } pub fn uniform3x3f(&mut self, name: &str, matrix: &[f32]) { assert_eq!(matrix.len(), 9); |