From 57dfb60fdc38d7b4815087d71781e2b326c6939b Mon Sep 17 00:00:00 2001 From: pommicket Date: Thu, 26 Jan 2023 13:38:12 -0500 Subject: more documentation --- gen_random/src/lib.rs | 6 ++ src/main.rs | 62 +++++++++---------- src/sdf.rs | 163 ++++++++++++++++++++++++++++++++++---------------- src/win.rs | 7 ++- 4 files changed, 155 insertions(+), 83 deletions(-) diff --git a/gen_random/src/lib.rs b/gen_random/src/lib.rs index 4bd67be..b75d8cc 100644 --- a/gen_random/src/lib.rs +++ b/gen_random/src/lib.rs @@ -4,7 +4,10 @@ use std::rc::Rc; use std::sync::Arc; use std::cell::{Cell, RefCell}; +/// parameters passed to [GenRandom] implementation pub trait GenRandomParams: Sized + Default + Copy { + /// If `params` is used to generate `object`, then `params.inc_depth()` will + /// be used to generate `object`'s members. fn inc_depth(self) -> Self; } @@ -14,6 +17,7 @@ impl GenRandomParams for () { } } + impl GenRandomParams for i64 { fn inc_depth(self) -> Self { self - 1 @@ -73,10 +77,12 @@ pub trait GenRandom: Sized { } } +/// generate a [Vec] of `len` random items. pub fn gen_random_vec>(rng: &mut impl Rng, len: usize) -> Vec { (0..len).map(|_| T::gen_random(rng)).collect() } +/// generate a [Vec] of `len` random items using `rand::thread_rng()`. pub fn gen_thread_random_vec>(len: usize) -> Vec { gen_random_vec(&mut rand::thread_rng(), len) } diff --git a/src/main.rs b/src/main.rs index 9627936..fa5e8f3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,5 @@ /* @TODO: -- documentation - is it the function generation or the shader compililng that's slow for large functions? - record a cool video */ @@ -21,7 +20,7 @@ use std::{ collections::HashMap, fs::{self, File}, io::{prelude::*, BufReader, BufWriter}, - time::{Instant, SystemTime} + time::{Instant, SystemTime}, }; use win::{ColorF32, ColorGrayscaleF32, ColorU8}; @@ -30,11 +29,14 @@ type Mat3 = Matrix3; type Mat4 = Matrix4; type Rot3 = Rotation3; +/// scale to draw menu at const MENU_SCALE: f32 = 0.6; + +/// button in escape menu #[derive(Clone, Copy)] enum MenuButton { Resume, - Quit + Quit, } /// array of buttons in menu.png. (y, height, button) const MENU_BUTTONS: &[(f32, f32, MenuButton)] = &[ @@ -42,6 +44,7 @@ const MENU_BUTTONS: &[(f32, f32, MenuButton)] = &[ (605.0, 165.0, MenuButton::Quit), ]; +/// an icon which will be displayed on screen #[repr(i32)] #[derive(Clone, Copy)] enum Icon { @@ -65,6 +68,7 @@ impl Icon { } } +/// current "view" of SDF (camera position, rotation, etc.) #[derive(Clone)] struct View { pos: Vec3, @@ -106,7 +110,7 @@ impl View { fn unpause(&mut self, rewind: bool) { self.time_speed = if rewind { -1.0 } else { 1.0 }; } - + /// returns true if the current time is modified fn pass_time(&mut self, dt: f64) -> bool { let dt = self.time_speed * dt; @@ -205,11 +209,11 @@ struct Settings { impl Settings { fn get_modified_time(&self) -> Option { - fs::metadata(&self.filename).ok() - .map(|m| m.modified().ok()) - .flatten() + fs::metadata(&self.filename) + .ok() + .and_then(|m| m.modified().ok()) } - + pub fn load(filename: &str) -> Result { let mut settings = Self { filename: filename.to_string(), @@ -219,11 +223,11 @@ impl Settings { settings.reload()?; Ok(settings) } - + /// Reload settings from file. On failure, the settings are left unchanged. fn reload(&mut self) -> Result<(), String> { self.file_last_modified = self.get_modified_time(); - + let mut new_data = HashMap::new(); let file = File::open(&self.filename).map_err(|e| format!("{e}"))?; let reader = BufReader::new(file); @@ -243,21 +247,16 @@ impl Settings { new_data.insert(key.to_string(), value); } } - + self.data = new_data; Ok(()) } - + /// reload settings if the settings file was changed. /// returns true if the settings were changed. pub fn reload_if_modified(&mut self) -> bool { if self.get_modified_time() != self.file_last_modified { - if self.reload().is_err() { - // we'll just keep the old settings. - false - } else { - true - } + self.reload().is_err() } else { false } @@ -552,7 +551,7 @@ impl State { self.main_array.draw(); } - + /// draw the main framebuffer to the screen, apply postprocessing fn render_post(&mut self) { let highlight_button = self.menu_button_at_pos(self.window.get_mouse_pos()); @@ -620,7 +619,7 @@ impl State { self.flash(Icon::Screenshot); Ok(()) } - + /// returns Some(button, v1, v2) which is a bit weird but oh well fn menu_button_at_pos(&self, screen_pos: (i32, i32)) -> Option<(MenuButton, f32, f32)> { let window_height = self.window.size().1 as f32; @@ -638,7 +637,7 @@ impl State { } None } - + fn press_menu_button(&mut self, button: MenuButton) { use MenuButton::*; match button { @@ -652,7 +651,7 @@ impl State { if self.settings.reload_if_modified() { self.needs_redraw = true; } - + if let Some(max_framerate) = self.settings.get_f32("max-framerate") { if max_framerate > 0.0 { let dt = self.frame_time.elapsed().as_secs_f32(); @@ -758,7 +757,12 @@ impl State { self.needs_redraw = true; } } - MouseButtonDown { button: MouseButton::Left, x, y, .. } => { + MouseButtonDown { + button: MouseButton::Left, + x, + y, + .. + } => { if self.esc_menu { if let Some((menu_button, _, _)) = self.menu_button_at_pos((x, y)) { self.press_menu_button(menu_button); @@ -769,10 +773,8 @@ impl State { } } - if !self.esc_menu { - if self.view.pass_time(frame_dt.into()) { - self.needs_redraw = true; - } + if !self.esc_menu && self.view.pass_time(frame_dt.into()) { + self.needs_redraw = true; } if !self.esc_menu { @@ -835,7 +837,7 @@ impl State { self.view.pos += motion; self.needs_redraw = true; } - + if dl != 0.0 { let level_set_amount = 1.0 * speed_multiplier; self.view.level_set += dl * level_set_amount; @@ -864,7 +866,7 @@ impl State { self.main_framebuffer_size = render_resolution; self.needs_redraw = true; } - + // this needs_redraw check stops the SDF from being rendered when // the escape menu is open for example (unless window dimensions/settings are changed) // this reduces GPU usage and gives a higher framerate @@ -872,7 +874,7 @@ impl State { self.render_main(); self.needs_redraw = false; } - + 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 diff --git a/src/sdf.rs b/src/sdf.rs index ad545b6..c140d1b 100644 --- a/src/sdf.rs +++ b/src/sdf.rs @@ -10,13 +10,17 @@ use serde::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize}; use std::fmt::{self, Display, Formatter, Write}; -// we're only writing numbers and strings so write! should never fail. +/// macro used to write to strings. +/// +/// we're only writing numbers and strings so write! should never fail. macro_rules! write_str { ($( $arg:tt )*) => { write!($($arg)*).unwrap() } } +/// parameters used to generate SDF #[derive(Copy, Clone)] pub struct SdfParams { + /// maximum expression depth max_depth: i32, } @@ -34,12 +38,14 @@ impl GenRandomParams for SdfParams { } } -/// these are constant across 3D space, not across time/user input/etc. +/// constants across 3D space (but not across time/user input/etc.) #[derive(Debug, GenRandom, Serialize, Deserialize, Clone, Copy)] #[params(SdfParams)] pub enum Constant { + /// a number #[prob(0.0)] F32(f32), + /// `Time(a, b) = a * time + b`, where `time` is in seconds #[prob(0.5)] Time( #[scale(0.2)] @@ -49,9 +55,15 @@ pub enum Constant { ), } +/// a trait for string serialization +/// +/// this is automatically implemented for anything with [Serialize] + [Deserialize]. pub trait ImportExport: Sized { + /// export self as a string fn export_string(&self) -> String; - /// returns None if `s` is not a valid string + /// import a string + /// + /// returns None if `s` is not a valid serialized string fn import_string(s: &str) -> Option; } @@ -65,6 +77,7 @@ fn encode_hex(data: &[u8]) -> String { } /// decode `data` from hexadecimal. +/// /// returns None if this isn't a valid hexadecimal string. fn decode_hex(data: &str) -> Option> { let data = data.replace(char::is_whitespace, ""); @@ -134,6 +147,7 @@ impl Display for Constant { } } +/// a `vec3` of [Constant]s #[derive(GenRandom, Debug, Serialize, Deserialize)] #[params(SdfParams)] pub struct Constant3(Constant, Constant, Constant); @@ -165,93 +179,124 @@ impl Display for Constant3 { } } +/// a generic function from float/vec*n* to float/vec*n* #[derive(GenRandom, Debug, Serialize, Deserialize)] #[params(SdfParams)] pub enum RnToRn { + /// 1/c sin(cx) #[prob(4)] #[bias(0.01)] // prevent division by 0 - Sin(Constant), // 1/c sin(cx) + Sin(Constant), + /// based on modulus function #[prob(4)] #[bias(0.01)] InfiniteMirrors(Constant), + /// arctan(c x) / c #[prob(2)] - Arctan(Constant), // arctan(c x) / c + Arctan(Constant), + /// based on 1/x² sin(x²) #[prob(2)] #[bias(0.01)] - SqSin(Constant), // based on 1/x² sin(x²) + SqSin(Constant), + /// based on sigmoid(x) = 1 / (1 + e^-x) #[prob(2)] #[bias(0.01)] - Sigmoid, //based on sigmoid(x) = 1 / (1 + e^-x) + Sigmoid, #[prob(2)] Wibbly, + /// based on sqrt(x) #[prob(2)] - Sqrt(Constant), + Sqrt(Constant), } +/// a function from vec3 to vec3 #[derive(GenRandom, Debug, Serialize, Deserialize)] #[params(SdfParams)] pub enum R3ToR3 { + /// the identity function f(x) = x #[prob(0)] Identity, + /// a composition of two functions #[prob(8)] #[only_if(params.max_depth >= 0)] Compose(Box, Box), + /// f(x, y, z) = (f₁(x), f₂(y), f₃(z)) #[prob(4)] #[only_if(params.max_depth >= 0)] PerComponent(Box, Box, Box), + /// a linear interpolation between two functions #[prob(4)] #[only_if(params.max_depth >= 0)] Mix(Box, Box, Constant), + /// translate by a constant amount f(x) = x + p #[prob(0.5)] Translate(Constant3), - // this was removed at some point. - // it doesn't really seem to be helpful. + /// this was removed at some point. + /// it doesn't really seem to be helpful. #[prob(0)] Twisty(Constant), + /// a generic function applied to vec3 #[prob(12)] NToN(Box), + /// rotate by a constant amount #[prob(0.5)] #[scale(2 * std::f32::consts::PI)] Rotate(Constant3), - + // ---- everything below has been moved to RnToRn and is only here for backwards compatibility ---- + #[doc(hidden)] #[prob(0)] Sin(Constant), // 1/c sin(cx) + #[doc(hidden)] #[prob(0)] InfiniteMirrors(Constant), + #[doc(hidden)] #[prob(0)] Arctan(Constant), + #[doc(hidden)] #[prob(0)] SqSin(Constant), + #[doc(hidden)] #[prob(0)] Sigmoid, + #[doc(hidden)] #[prob(0)] Wibbly, + #[doc(hidden)] #[prob(0)] - Sqrt(Constant), + Sqrt(Constant), } +/// a function from float to float #[derive(GenRandom, Debug, Serialize, Deserialize)] #[params(SdfParams)] pub enum RToR { + /// the identity function f(x) = x #[prob(1)] Identity, + /// composition of two functions #[prob(0)] #[only_if(params.max_depth >= 0)] Compose(Box, Box), + /// subtract a constant #[prob(0)] Subtract(Constant), + /// a generic function applied to float #[prob(2)] - NToN(Box) + NToN(Box), } +/// a function from vec3 to float #[derive(GenRandom, Debug, Serialize, Deserialize)] #[params(SdfParams)] pub enum R3ToR { + /// SDF for a sphere #[prob(0.1)] Sphere(Constant), + /// SDF for a cube #[prob(0.1)] Cube(Constant), + /// SDF for a box frame #[prob(0.1)] BoxFrame { #[scale(3.0)] @@ -259,6 +304,7 @@ pub enum R3ToR { #[scale(0.2)] thickness: Constant, }, + /// SDF for a torus #[prob(0.1)] Torus { #[scale(3.0)] @@ -266,72 +312,92 @@ pub enum R3ToR { #[scale(0.2)] thickness: Constant, }, + /// SDF for a triangular prism #[prob(0.1)] TriPrism(Constant, Constant), + /// SDF for a vertical line segment #[prob(0.1)] VLineSegment(Constant), + /// SDF for a cylinder #[prob(0.1)] Cylinder(Constant, Constant), + /// apply a function from vec3 to vec3, then vec3 to float, then float to float #[prob(8)] #[only_if(params.max_depth >= 0)] Compose(Box, Box, Box), + /// linear interpolation between two functions #[prob(4)] #[only_if(params.max_depth >= 0)] Mix(Box, Box, Constant), + /// sin(f(x))·cos(g(x)) #[prob(4)] #[only_if(params.max_depth >= 0)] SinCos(Box, Box), + /// "smooth" minimum of two functions #[prob(2)] #[only_if(params.max_depth >= 0)] SmoothMin(Box, Box), + /// minimum of two functions #[prob(2)] #[only_if(params.max_depth >= 0)] Min(Box, Box), + /// f(x,y,z) = x #[prob(0.1)] ProjectX, + /// f(x,y,z) = y #[prob(0.1)] ProjectY, + /// f(x,y,z) = z #[prob(0.1)] ProjectZ, } impl R3ToR3 { + /// create composition of two functions pub fn compose(self, b: Self) -> Self { Self::Compose(Box::new(self), Box::new(b)) } } impl RToR { + /// create composition of two functions pub fn compose(self, b: Self) -> Self { Self::Compose(Box::new(self), Box::new(b)) } } impl R3ToR { + /// create sphere SDF pub fn sphere_f32(r: f32) -> Self { Self::Sphere(r.into()) } + /// create cube SDF pub fn cube_f32(r: f32) -> Self { Self::Cube(r.into()) } + /// create mix of two SDFs pub fn mix(a: Self, b: Self, t: Constant) -> Self { Self::Mix(Box::new(a), Box::new(b), t) } + /// create mix of two SDFs pub fn mix_f32(a: Self, b: Self, t: f32) -> Self { Self::mix(a, b, t.into()) } + /// create minimum of two SDFs pub fn min(a: Self, b: Self) -> Self { Self::Min(Box::new(a), Box::new(b)) } + /// create "smooth" minimum of two SDFs pub fn smooth_min(a: Self, b: Self) -> Self { Self::SmoothMin(Box::new(a), Box::new(b)) } + /// create composition of three functions pub fn compose(pre: R3ToR3, f: Self, post: RToR) -> Self { Self::Compose(Box::new(pre), Box::new(f), Box::new(post)) } @@ -355,6 +421,7 @@ impl Default for R3ToR { } } +/// a variable in a GLSL program #[derive(Clone, Copy)] struct Variable { id: u32, @@ -366,6 +433,7 @@ impl Display for Variable { } } +/// a counter used to create variables struct VarCounter { idx: u32, } @@ -375,6 +443,7 @@ impl VarCounter { Self { idx: 0 } } + /// get next variable fn next(&mut self) -> Variable { let ret = Variable { id: self.idx }; self.idx += 1; @@ -398,6 +467,7 @@ impl fmt::Display for GLSLType { } } +/// a trait implemented by [R3ToR3], [R3ToR], etc. trait Function: Sized + Default + GenRandom + ImportExport { /// appends `code` with glsl code to apply the function to the input variable. /// returns the output variable. @@ -423,6 +493,7 @@ trait Function: Sized + Default + GenRandom + ImportExport { write_str!(code, "return {output};\n}}\n\n"); } + /// generates a random function with the given length fn good_random(rng: &mut impl Rng, function_length: usize) -> Self { let default_len = Self::default().export_string().len(); for max_depth in 1.. { @@ -449,12 +520,10 @@ trait Function: Sized + Default + GenRandom + ImportExport { return selected.1; } // weird that rust thinks 1.. "might have zero elements to iterate on" - // but technically this can happen if max_depth reaches usize::MAX - // i'm not really worried about that though - // we'd have much bigger problems before then. panic!("wtf") } + /// generates a random function with the given length using the thread random number generator fn good_thread_random(function_length: usize) -> Self { Self::good_random(&mut rand::thread_rng(), function_length) } @@ -474,7 +543,10 @@ impl RnToRn { Arctan(c) => { let output = var.next(); // we need to scale arctan(cx) so it doesn't break the SDF - write_str!(code, "{type} {output} = (1.0 / {c}) * atan({c} * {input});\n"); + write_str!( + code, + "{type} {output} = (1.0 / {c}) * atan({c} * {input});\n" + ); output } SqSin(c) => { @@ -514,11 +586,13 @@ impl RnToRn { } Sin(c) => { let output = var.next(); - write_str!(code, "{type} {output} = sin({c} * {input}) * (1.0 / {c});\n"); + write_str!( + code, + "{type} {output} = sin({c} * {input}) * (1.0 / {c});\n" + ); output } InfiniteMirrors(c) => { - // similar to Sin(c), but uses mod instead let q = var.next(); let r = var.next(); let output = var.next(); @@ -552,9 +626,7 @@ impl Function for RToR { let a_output = a.to_glsl(input, code, var); b.to_glsl(a_output, code, var) } - NToN(f) => { - f.to_glsl(input, code, var, 1) - } + NToN(f) => f.to_glsl(input, code, var, 1), } } } @@ -582,15 +654,19 @@ impl Function for R3ToR3 { let y_input = var.next(); let z_input = var.next(); let output = var.next(); - write_str!(code, + write_str!( + code, "float {x_input} = {input}.x;\n float {y_input} = {input}.y;\n - float {z_input} = {input}.z;\n"); + float {z_input} = {input}.z;\n" + ); let x_output = fx.to_glsl(x_input, code, var); let y_output = fy.to_glsl(y_input, code, var); let z_output = fz.to_glsl(z_input, code, var); - write_str!(code, - "vec3 {output} = vec3({x_output}, {y_output}, {z_output});\n"); + write_str!( + code, + "vec3 {output} = vec3({x_output}, {y_output}, {z_output});\n" + ); output } Mix(a, b, t) => { @@ -640,30 +716,14 @@ impl Function for R3ToR3 { ); output } - NToN(f) => { - f.to_glsl(input, code, var, 3) - } - Sin(c) => { - RnToRn::Sin(*c).to_glsl(input, code, var, 3) - } - InfiniteMirrors(c) => { - RnToRn::InfiniteMirrors(*c).to_glsl(input, code, var, 3) - } - SqSin(c) => { - RnToRn::SqSin(*c).to_glsl(input, code, var, 3) - } - Arctan(c) => { - RnToRn::Arctan(*c).to_glsl(input, code, var, 3) - } - Sigmoid => { - RnToRn::Sigmoid.to_glsl(input, code, var, 3) - } - Wibbly => { - RnToRn::Wibbly.to_glsl(input, code, var, 3) - } - Sqrt(c) => { - RnToRn::Sqrt(*c).to_glsl(input, code, var, 3) - } + NToN(f) => f.to_glsl(input, code, var, 3), + Sin(c) => RnToRn::Sin(*c).to_glsl(input, code, var, 3), + InfiniteMirrors(c) => RnToRn::InfiniteMirrors(*c).to_glsl(input, code, var, 3), + SqSin(c) => RnToRn::SqSin(*c).to_glsl(input, code, var, 3), + Arctan(c) => RnToRn::Arctan(*c).to_glsl(input, code, var, 3), + Sigmoid => RnToRn::Sigmoid.to_glsl(input, code, var, 3), + Wibbly => RnToRn::Wibbly.to_glsl(input, code, var, 3), + Sqrt(c) => RnToRn::Sqrt(*c).to_glsl(input, code, var, 3), } } } @@ -819,11 +879,13 @@ impl R3ToR3 { } } +/// options for generating a [Scene] pub struct SceneConfig { pub sdf_length: usize, pub color_length: usize, } +/// a "scene" (includes SDF and color function) #[derive(Serialize, Deserialize, Default)] pub struct Scene { pub sdf: R3ToR, @@ -831,6 +893,7 @@ pub struct Scene { } impl Scene { + /// generate a random scene pub fn good_random(rng: &mut impl Rng, config: &SceneConfig) -> Self { let sdf = R3ToR::good_random(rng, config.sdf_length); let color_function = R3ToR3::good_random(rng, config.color_length); diff --git a/src/win.rs b/src/win.rs index 08b0b05..a659b8e 100644 --- a/src/win.rs +++ b/src/win.rs @@ -1,3 +1,4 @@ +/// functions for opening windows and doing OpenGL stuff // all OpenGL calls are done through the Window. // this is because OpenGL is not thread safe. use crate::sdl; @@ -478,15 +479,15 @@ pub enum Event { x: i32, y: i32, /// 1 for single-click, 2 for double-click, etc. - clicks: u8 + clicks: u8, }, MouseButtonUp { button: MouseButton, x: i32, y: i32, /// 1 for single-click, 2 for double-click, etc. - clicks: u8 - } + clicks: u8, + }, } pub fn display_error_message(message: &str) { -- cgit v1.2.3