summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/fshader_main.glsl55
-rw-r--r--src/main.rs43
-rw-r--r--src/sdf.rs2
-rw-r--r--src/sdl.rs75
-rw-r--r--src/win.rs14
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);
diff --git a/src/sdf.rs b/src/sdf.rs
index 98f82da..1ced6b0 100644
--- a/src/sdf.rs
+++ b/src/sdf.rs
@@ -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(
diff --git a/src/sdl.rs b/src/sdl.rs
index 6d41911..9971e0a 100644
--- a/src/sdl.rs
+++ b/src/sdl.rs
@@ -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(())
}
diff --git a/src/win.rs b/src/win.rs
index 8f031f0..5783b1c 100644
--- a/src/win.rs
+++ b/src/win.rs
@@ -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);