diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | README.md | 21 | ||||
-rw-r--r-- | settings.txt | 23 | ||||
-rw-r--r-- | src/fshader_main.glsl | 11 | ||||
-rw-r--r-- | src/main.rs | 105 | ||||
-rw-r--r-- | src/sdf.rs | 191 |
6 files changed, 249 insertions, 103 deletions
@@ -4,3 +4,4 @@ tags *.out *.dll *.lib +scenes.txt @@ -4,8 +4,27 @@ procedurally generated signed distance fields ## controls -- WASD/arrow keys to move + QE to move up/down +- move mouse to look around +- W,A,S,D/arrow keys to move forward/backwards/left/right + Q,E/PageUp,PageDown to move up/down +- R to create a new SDF - use this a lot! most SDFs are boring! +- Space to "unpause time" (start animating). On some SDFs this might not do much. + Press space again to pause time, and shift+space to rewind time. +- 0 to reset location + time - =/- (equals/minus) to change the level set - Ctrl+C to copy SDF to clipboard, Ctrl+V to paste SDF from clipboard. On Linux if you close the application the clipboard contents will be lost (thanks a lot X11). - F to go fullscreen + +## saving SDFs + +if you get an SDF which looks cool, you can copy it with Ctrl+C, and save it somewhere/share it with +your friends. + +also a list of SDFs is stored in `scenes.txt`. + +## info + +AutoSDF can be configured by editing `settings.txt`. + +If AutoSDF is running slow, you should make `sdf-length` and `color-length` smaller. +If it's running fast, and you want more interesting shapes, you should increase them. diff --git a/settings.txt b/settings.txt new file mode 100644 index 0000000..fa8db82 --- /dev/null +++ b/settings.txt @@ -0,0 +1,23 @@ +mouse-sensitivity 50 +# complexity of SDF +# making this bigger will make more complex scenes, but will require more gpu computation +sdf-length 500 +# complexity of color function +# making this bigger will make more complex colors, but will require more gpu computation +color-length 300 +# max raymarching iterations +# making this bigger will make boundaries less "blurry", but will require more gpu computation +max-iterations 30 +# raymarching distance threshold +# (how close to distance 0 should the SDF get before we draw?) +distance-threshold 0.02 +# antialiasing factors +# e.g. use 2 for 2x2 MSAA +# this will make the scene look a bit nicer, but will require MUCH MORE gpu computation. +antialiasing 1 +# focal length in "meters" +focal-length 1 +# field of view in degrees +fov 45 +# set this to 1 to use HSV instead of RGB for color +hsv 0 diff --git a/src/fshader_main.glsl b/src/fshader_main.glsl index bcd0736..aa43b27 100644 --- a/src/fshader_main.glsl +++ b/src/fshader_main.glsl @@ -7,7 +7,10 @@ uniform float u_time; uniform float u_fov; uniform float u_focal_length; uniform float u_level_set; +uniform float u_distance_threshold; uniform int u_hsv; +uniform ivec2 u_antialiasing; +uniform int u_iterations; %COMMON% %SDF% @@ -37,9 +40,9 @@ vec3 get_color(vec3 p) { } } -#define ITERATIONS 30 -#define AA_X 1 -#define AA_Y 1 +#define ITERATIONS u_iterations +#define AA_X u_antialiasing.x +#define AA_Y u_antialiasing.y float sdf_adjusted(vec3 p) { @@ -84,7 +87,7 @@ void main() { p += dist * delta; } - float threshold = 0.02; + float threshold = u_distance_threshold; if (min_dist < threshold) { vec3 N = normal(p); // light direction = towards user diff --git a/src/main.rs b/src/main.rs index 5837f7e..4891327 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,18 +2,13 @@ @TODO: - options for: - max framerate - - mouse sensitivity - - fov, focal length - - AA quality - - # iterations, distance cutoff ----release--- - [ and ] to move through time - switch framebuffer texture to grayscale - show that θ = σ(z) / sqrt(x² + y²) (x,y,z) → (x cosθ + y sinθ, y cosθ - x sinθ, z) is lipschitz continuous, & add it - feedback for copy/paste (flash screen or something) -- feedback for pause/unpause/rewind +- feedback for pause/unpause/rewind (flash icons) - Params instead of depth for GenRandom - allow multiple endpoints (cube & sphere & ...) - save seeds to a file then let user go back&forth through past sdfs @@ -42,8 +37,12 @@ a263736466a167436f6d706f736583a165537153696ea163463332fa3e784c98a1634d697883a166 a263736466a1634d696e82a1634d696e82a166537068657265a163463332fa3f2365aca1634d697883a167436f6d706f736583a16641726374616ea163463332fa3eb04a5ca167436f6d706f736583675369676d6f6964a169536d6f6f74684d696e82a167436f6d706f736583684964656e74697479a166537068657265a163463332fa3eeb25b8684964656e74697479a167436f6d706f736583684964656e74697479a166537068657265a163463332fa3f10d6a2684964656e74697479684964656e74697479684964656e74697479a165546f727573a266726164697573a163463332fa3d9099f069746869636b6e657373a163463332fa3e00b102a163463332fa3f06b5a2a1634d696e82a165546f727573a266726164697573a163463332fa40121b0169746869636b6e657373a163463332fa3e32f4faa1634d697883a16443756265a163463332fa3f7f9dc8a1634d697883a165546f727573a266726164697573a163463332fa3f9286e369746869636b6e657373a163463332fa3d8a3f27a167436f6d706f736583a167436f6d706f736582a167436f6d706f736582684964656e74697479684964656e74697479a16f496e66696e6974654d6972726f7273a163463332fa3ea62688a168426f784672616d65a26473697a65a163463332fa3fc0fb4969746869636b6e657373a163463332fa3e472462684964656e74697479a163463332fa3f69a73ea163463332fa3f5b9c9e6e636f6c6f725f66756e6374696f6ea16f496e66696e6974654d6972726f7273a163463332fa3ef4ea8c a263736466a169536d6f6f74684d696e82a1634d696e82a167436f6d706f736583a167436f6d706f736582a16641726374616ea163463332fa3e6d7230a1695472616e736c61746583a163463332fa3e7262f8a163463332fa3eece0eca163463332fa3f49c42ca168426f784672616d65a26473697a65a163463332fa3ff3ee1169746869636b6e657373a163463332fa3df6dfed684964656e74697479a167436f6d706f736583a16f496e66696e6974654d6972726f7273a163463332fa3f2a2de8a165546f727573a266726164697573a163463332fa3fc93f1e69746869636b6e657373a163463332fa3e0fa700684964656e74697479a169536d6f6f74684d696e82a169536d6f6f74684d696e82a1634d696e82a169536d6f6f74684d696e82a167436f6d706f736583675369676d6f6964a1634d696e82a166537068657265a163463332fa3e0586b0a166537068657265a163463332fa3f4d6214684964656e74697479a167436f6d706f736583a167436f6d706f736582684964656e74697479684964656e74697479a1634d697883a166537068657265a163463332fa3f5c46e6a166537068657265a163463332fa3f3f4896a163463332fa3f10ba30684964656e74697479a166537068657265a163463332fa3ef3b604a167436f6d706f736583a165537153696ea163463332fa3ef1bfb8a1634d697883a1634d697883a168426f784672616d65a26473697a65a163463332fa3ee4801069746869636b6e657373a163463332fa3dc84d37a168426f784672616d65a26473697a65a163463332fa4019d5d269746869636b6e657373a163463332fa3d5d0307a163463332fa3eb7554ca167436f6d706f736583a165537153696ea163463332fa3f31c33ca167436f6d706f736583684964656e74697479a166537068657265a163463332fa3e7db3c0684964656e74697479684964656e74697479a163463332fa3f3176c0684964656e74697479a165546f727573a266726164697573a163463332fa3f253f0c69746869636b6e657373a163463332fa3c96c48d6e636f6c6f725f66756e6374696f6ea16f496e66696e6974654d6972726f7273a163463332fa3ea219f4 a263736466a169536d6f6f74684d696e82a1634d697883a167436f6d706f736583a16641726374616ea16454696d6582fa3da97b73fa3eddf3cca167436f6d706f736583a16f496e66696e6974654d6972726f7273a16454696d6582fabb6c5400fa3c230980a167436f6d706f736583675369676d6f6964a167436f6d706f736583a167436f6d706f736582684964656e74697479684964656e74697479a16443756265a163463332fa3edf6864684964656e74697479684964656e74697479684964656e74697479684964656e74697479a166537068657265a163463332fa3ee32af4a16454696d6582fa3db4b603fa3ea79394a1634d697883a167436f6d706f736583a16353696ea163463332fa3de4e160a167436f6d706f736583a16f496e66696e6974654d6972726f7273a163463332fa3f7493fea168426f784672616d65a26473697a65a16454696d6582fa3e92c08dfa3ee156b869746869636b6e657373a16454696d6582fa3c74f202fa3df09e1d684964656e74697479684964656e74697479a1634d697883a16443756265a163463332fa3f5f8194a1634d696e82a1634d697883a167436f6d706f736583675369676d6f6964a168426f784672616d65a26473697a65a163463332fa3fe3873769746869636b6e657373a163463332fa3e3524e7684964656e74697479a1634d696e82a167436f6d706f736583684964656e74697479a166537068657265a163463332fa3f50ffb8684964656e74697479a167436f6d706f736583684964656e74697479a166537068657265a163463332fa3c693200684964656e74697479a163463332fa3e5d3250a168426f784672616d65a26473697a65a163463332fa3fb01f6669746869636b6e657373a16454696d6582fabc5d9d48fa3d1cc993a163463332fa3f394d40a16454696d6582fabda7629afa3eefd35c6e636f6c6f725f66756e6374696f6ea16f496e66696e6974654d6972726f7273a163463332fa3d8dc730 +a263736466a1634d697883a167436f6d706f736583a1695472616e736c61746583a16454696d6582fa3c1c5ee8fa3f1f076ea16454696d6582fabc0cdbe8fa3ea36374a16454696d6582fa3cdd78a4fa3ef7d550a167436f6d706f736583a16641726374616ea16454696d6582fabbdfeb60fa3dc83a60a167436f6d706f736583a16f496e66696e6974654d6972726f7273a16454696d6582fabd054634fa3e4c5448a167436f6d706f736583a16f496e66696e6974654d6972726f7273a163463332fa3f0b5ffaa168426f784672616d65a26473697a65a163463332fa3f25fee269746869636b6e657373a16454696d6582fabc18bba9fa3e319347684964656e74697479684964656e74697479684964656e74697479684964656e74697479a1634d697883a168426f784672616d65a26473697a65a163463332fa40190a4e69746869636b6e657373a16454696d6582fa3b46bd33fa3cec529aa167436f6d706f736583a1695472616e736c61746583a16454696d6582fa3c91b74cfa3d0ece20a16454696d6582fa3da30eedfa3f261c72a163463332fa3f295ec6a167436f6d706f736583a167436f6d706f736582a16353696ea163463332fa3d069a00a16f496e66696e6974654d6972726f7273a16454696d6582fa3dae4de1fa3d837a30a1634d697883a167436f6d706f736583a16353696ea163463332fa3f7730b6a167436f6d706f736583684964656e74697479a166537068657265a163463332fa3e01c7a8684964656e74697479684964656e74697479a16443756265a16454696d6582fabcfb0758fa3e7763f0a16454696d6582fabd650fcdfa3f0d466e684964656e74697479684964656e74697479a16454696d6582fabd78a3fafa3dd86e20a16454696d6582fabd6ccb9afa3f1f06e46e636f6c6f725f66756e6374696f6ea167436f6d706f736582a167436f6d706f736582a16641726374616ea163463332fa3f67a3a2a16641726374616ea163463332fa3f287b4e675369676d6f6964 +a263736466a1634d697883a169536d6f6f74684d696e82a167436f6d706f736583a166526f7461746583a16454696d6582fa3ce93344fa3f037de0a16454696d6582fabcf59274fa3f605e9ca16454696d6582fabd5d556dfa3f13891ca167436f6d706f736583a167436f6d706f736582a167436f6d706f736582a16641726374616ea163463332fa3da39b50a167436f6d706f736582684964656e74697479684964656e74697479a167436f6d706f736582a16353696ea163463332fa3e785a08a167436f6d706f736582684964656e74697479684964656e74697479a167436f6d706f736583a166526f7461746583a163463332fa3ec36808a163463332fa3e1fdf58a163463332fa3e69ed18a165546f727573a266726164697573a163463332fa3fb2f5de69746869636b6e657373a163463332fa3e22de5d684964656e74697479684964656e74697479684964656e74697479a167436f6d706f736583a16353696ea163463332fa3ea5ea70a168426f784672616d65a26473697a65a16454696d6582fabe10ed3efa3eb1cfd869746869636b6e657373a163463332fa3e0dea57684964656e74697479a168426f784672616d65a26473697a65a16454696d6582fabd741592fa401a9ae469746869636b6e657373a16454696d6582fa3c677072fa3e3bc95da16454696d6582fa3b24dd40fa3e816f846e636f6c6f725f66756e6374696f6ea165537153696ea16454696d6582fabc7f2b18fa3f6e73c2 */ +// LICENSE: i'm not gonna sue you for "copyright infringement". go wild. + extern crate nalgebra; pub mod sdf; @@ -51,8 +50,9 @@ mod sdl; pub mod win; use nalgebra::{Matrix3, Matrix4, Rotation3, Vector3}; -use std::time::Instant; +use std::{time::Instant, fs::File, io::{BufReader, prelude::*}, collections::HashMap}; use win::ColorF32; +use sdf::ImportExport; type Vec3 = Vector3<f32>; type Mat3 = Matrix3<f32>; @@ -156,9 +156,6 @@ impl Programs { .replace("%SDF%", &sdf) .replace("%COMMON%", source_common); - //println!("{fshader_source}"); - println!("scene: {}", scene.export_string()); - window .link_program( &mut self.main, @@ -199,9 +196,58 @@ fn get_rng() -> impl rand::Rng { const TEST_HEIGHT: u16 = 100; const TEST_WIDTH: u16 = 100; +#[derive(Default)] +struct Settings { + data: HashMap<String, f64> +} + +impl Settings { + fn load(filename: &str) -> Result<Self, String> { + let file = File::open(filename).map_err(|e| format!("{e}"))?; + let reader = BufReader::new(file); + let mut data = HashMap::new(); + for line in reader.lines() { + let full_line = line.map_err(|e| format!("{e}"))?; + let line = full_line.trim(); + if line.starts_with('#') { + // comment + } else { + let parts: Vec<&str> = line.split(' ').collect(); + if parts.len() != 2 { + return Err(format!("bad line: {line}")); + } + let key = parts[0].trim(); + let value = parts[1].trim(); + let value: f64 = value.parse().map_err(|_| format!("bad number: {value}"))?; + data.insert(key.to_string(), value); + } + } + Ok(Self { + data + }) + } + + fn get_f64(&self, key: &str) -> Option<f64> { + self.data.get(key).copied() + } + + fn get_f32(&self, key: &str) -> Option<f32> { + self.get_f64(key).map(|x| x as f32) + } + + fn get_usize(&self, key: &str) -> Option<usize> { + self.get_f64(key).map(|x| x as usize) + } + + fn get_i32(&self, key: &str) -> Option<i32> { + self.get_f64(key).map(|x| x as i32) + } +} + struct State { window: win::Window, view: View, + settings: Settings, initial_view: View, show_debug_info: bool, fullscreen: bool, @@ -209,6 +255,8 @@ struct State { programs: Programs, config: sdf::SceneConfig, scene: sdf::Scene, + // can be none if opening failed for whatever reason + scene_list: Option<File>, framebuffer_texture: win::Texture, framebuffer: win::Framebuffer, main_array: win::VertexArray, @@ -216,13 +264,13 @@ struct State { } impl State { - fn new() -> Result<Self, String> { + fn new(settings: Settings) -> Result<Self, String> { let mut window = win::Window::new("AutoSDF", 1280, 720, &Default::default()) .map_err(|e| format!("Error creating window: {e}"))?; let mut programs = Programs::new(&mut window); let config = sdf::SceneConfig { - sdf_max_depth: 7, - color_max_depth: 6, + sdf_length: settings.get_usize("sdf-length").unwrap_or(500), + color_length: settings.get_usize("color-length").unwrap_or(300), }; let scene = sdf::Scene::good_random(&mut get_rng(), &config); programs @@ -261,6 +309,12 @@ impl State { let test_array = window.create_vertex_array(test_buffer, &programs.test); window.set_mouse_relative(true); + + let scene_list = File::options() + .append(true) + .create(true) + .open("scenes.txt") + .ok(); let mut me = Self { window, @@ -276,6 +330,8 @@ impl State { framebuffer, main_array, test_array, + scene_list, + settings, }; me.load_scene(scene); Ok(me) @@ -285,6 +341,13 @@ impl State { match self.programs.load_scene(&mut self.window, &scene) { Ok(()) => { self.scene = scene; + if let Some(list) = &mut self.scene_list { + let mut string = self.scene.export_string(); + string.push('\n'); + // i dont really care if this fails, and it probably won't + let _ = list.write_all(string.as_bytes()); + } + // *technically speaking* the location of v_pos could change between reloads self.window.array_attrib2f(&mut self.main_array, "v_pos", 0); self.window.array_attrib2f(&mut self.test_array, "v_pos", 0); @@ -388,7 +451,7 @@ impl State { } } MouseMotion { xrel, yrel, .. } => { - let mouse_sensitivity = 0.05; + let mouse_sensitivity = 0.001 * self.settings.get_f32("mouse-sensitivity").unwrap_or(50.0); self.view .yaw_by(-xrel as f32 * mouse_sensitivity * frame_dt); self.view @@ -457,10 +520,14 @@ impl State { window.use_program(&self.programs.main); window.uniform1f("u_aspect_ratio", window.aspect_ratio()); window.uniform1f("u_time", view.time as f32); - window.uniform1f("u_fov", std::f32::consts::PI * 0.25); - window.uniform1f("u_focal_length", 1.0); + window.uniform1f("u_fov", self.settings.get_f32("fov").unwrap_or(45.0).to_radians()); + window.uniform1f("u_focal_length", self.settings.get_f32("focal-length").unwrap_or(1.0)); window.uniform1f("u_level_set", view.level_set); - window.uniform1i("u_hsv", 0); + window.uniform1i("u_hsv", self.settings.get_i32("hsv").unwrap_or(0)); + let antialiasing = self.settings.get_i32("antialiasing").unwrap_or(1); + window.uniform2i("u_antialiasing", antialiasing, antialiasing); + window.uniform1i("u_iterations", self.settings.get_i32("max-iterations").unwrap_or(30)); + window.uniform1f("u_distance_threshold", self.settings.get_f32("distance-threshold").unwrap_or(0.02)); window.uniform3x3f("u_rotation", view.rotation().as_slice()); window.uniform3f_slice("u_translation", view.pos.as_slice()); @@ -476,7 +543,9 @@ impl State { } fn try_main() -> Result<(), String> { - let mut state = State::new()?; + let settings = Settings::load("settings.txt").map_err(|e| + format!("Error loading settings.txt: {e}"))?; + let mut state = State::new(settings)?; while state.frame() {} Ok(()) @@ -6,6 +6,7 @@ extern crate serde_cbor; use gen_random::GenRandom; use gen_random_proc_macro::GenRandom; use rand::Rng; +use serde::{Deserialize, Serialize}; use serde_derive::{Deserialize, Serialize}; use std::fmt::{self, Display, Formatter, Write}; @@ -28,6 +29,53 @@ pub enum Constant { ), } +pub trait ImportExport: Sized { + fn export_string(&self) -> String; + /// returns None if `s` is not a valid string + fn import_string(s: &str) -> Option<Self>; +} + +/// encode `data` in hexadecimal +fn encode_hex(data: &[u8]) -> String { + let mut s = String::with_capacity(data.len() * 2); + for byte in data { + write_str!(s, "{byte:02x}"); + } + s +} + +/// decode `data` from hexadecimal. +/// returns None if this isn't a valid hexadecimal string. +fn decode_hex(data: &str) -> Option<Vec<u8>> { + let data = data.trim(); + if data.len() % 2 != 0 { + return None; + } + + let mut bytes = Vec::with_capacity(data.len() / 2); + for i in 0..data.len() / 2 { + let s = data.get(2 * i..2 * i + 2)?; + let byte = u8::from_str_radix(s, 16).ok()?; + bytes.push(byte); + } + Some(bytes) +} + +impl<T: Serialize + for <'a> Deserialize<'a>> ImportExport for T { + fn export_string(&self) -> String { + let mut data: Vec<u8> = vec![]; + // write errors should never happen + // that said, we don't want to panic if for whatever reason this fails. + let _ = serde_cbor::to_writer(&mut data, self); + encode_hex(&data) + } + + fn import_string(s: &str) -> Option<Self> { + let bytes = decode_hex(s)?; + serde_cbor::from_reader(&bytes[..]).ok()? + } +} + impl From<f32> for Constant { fn from(x: f32) -> Self { Self::F32(x) @@ -207,6 +255,25 @@ impl R3ToR { } } +impl Default for RToR { + fn default() -> Self { + Self::Identity + } +} + +impl Default for R3ToR3 { + fn default() -> Self { + Self::Identity + } +} + + +impl Default for R3ToR { + fn default() -> Self { + Self::Sphere(Constant::F32(1.0)) + } +} + #[derive(Clone, Copy)] struct Variable { id: u32, @@ -250,7 +317,7 @@ impl fmt::Display for GLSLType { } } -trait Function: Sized + GenRandom { +trait Function: Sized + Default + GenRandom + ImportExport { /// appends `code` with glsl code to apply the function to the input variable. /// returns the output variable. #[must_use] @@ -275,25 +342,39 @@ trait Function: Sized + GenRandom { write_str!(code, "return {output};\n}}\n\n"); } - fn good_random(rng: &mut impl Rng, max_depth: isize) -> Self { - // to make sure the function isn't too boring or too slow, - // we'll generate a bunch then take the one with the median code length. - let mut functions = vec![]; - for _i in 0..20 { - let f = Self::gen_random_max_depth(rng, max_depth); - let mut code = String::new(); - let mut var = VarCounter::new(); - let _ = f.to_glsl(var.next(), &mut code, &mut var); - let len = code.len(); - - functions.push((len, f)); + fn good_random(rng: &mut impl Rng, function_length: usize) -> Self { + let default_len = Self::default().export_string().len(); + for max_depth in 1.. { + let mut functions = vec![]; + for _i in 0..20 { + let f = Self::gen_random_max_depth(rng, max_depth); + let len = f.export_string().len().saturating_sub(default_len); + functions.push((len, f)); + } + functions.sort_by_key(|&(len, _)| len); + if functions[functions.len() - 1].0 < function_length { + // max_depth isn't large enough to get functions of this complexity + continue; + } + let mut closest = 0; + for (i, (len, _)) in functions.iter().enumerate() { + if len.abs_diff(function_length) < functions[closest].0.abs_diff(function_length) { + closest = i; + } + } + let selected = functions.remove(closest); + + return selected.1; } - functions.sort_by_key(|x| x.0); - functions.remove(functions.len() / 2).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") } - fn good_thread_random(max_depth: isize) -> Self { - Self::good_random(&mut rand::thread_rng(), max_depth) + fn good_thread_random(function_length: usize) -> Self { + Self::good_random(&mut rand::thread_rng(), function_length) } } @@ -489,39 +570,13 @@ impl Function for R3ToR { } } -/// encode `data` in hexadecimal -fn encode_hex(data: &[u8]) -> String { - let mut s = String::with_capacity(data.len() * 2); - for byte in data { - write_str!(s, "{byte:02x}"); - } - s -} - -/// decode `data` from hexadecimal. -/// returns None if this isn't a valid hexadecimal string. -fn decode_hex(data: &str) -> Option<Vec<u8>> { - let data = data.trim(); - if data.len() % 2 != 0 { - return None; - } - - let mut bytes = Vec::with_capacity(data.len() / 2); - for i in 0..data.len() / 2 { - let s = data.get(2 * i..2 * i + 2)?; - let byte = u8::from_str_radix(s, 16).ok()?; - bytes.push(byte); - } - Some(bytes) -} - impl R3ToR { - pub fn good_random(rng: &mut impl Rng, max_depth: isize) -> Self { - <Self as Function>::good_random(rng, max_depth) + pub fn good_random(rng: &mut impl Rng, length: usize) -> Self { + <Self as Function>::good_random(rng, length) } - pub fn good_thread_random(max_depth: isize) -> Self { - <Self as Function>::good_thread_random(max_depth) + pub fn good_thread_random(length: usize) -> Self { + <Self as Function>::good_thread_random(length) } pub fn to_glsl_function(&self, name: &str, code: &mut String) { @@ -530,12 +585,12 @@ impl R3ToR { } impl R3ToR3 { - pub fn good_random(rng: &mut impl Rng, max_depth: isize) -> Self { - <Self as Function>::good_random(rng, max_depth) + pub fn good_random(rng: &mut impl Rng, length: usize) -> Self { + <Self as Function>::good_random(rng, length) } - pub fn good_thread_random(max_depth: isize) -> Self { - <Self as Function>::good_thread_random(max_depth) + pub fn good_thread_random(length: usize) -> Self { + <Self as Function>::good_thread_random(length) } pub fn to_glsl_function(&self, name: &str, code: &mut String) { @@ -544,44 +599,20 @@ impl R3ToR3 { } pub struct SceneConfig { - pub sdf_max_depth: isize, - pub color_max_depth: isize, + pub sdf_length: usize, + pub color_length: usize, } -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Default)] pub struct Scene { pub sdf: R3ToR, pub color_function: R3ToR3, } -impl Default for Scene { - /// a sphere. pretty boring - fn default() -> Self { - Self { - sdf: R3ToR::Sphere(Constant::F32(1.0)), - color_function: R3ToR3::Identity, - } - } -} - impl Scene { - pub fn export_string(&self) -> String { - let mut data: Vec<u8> = vec![]; - // write errors should never happen - // that said, we don't want to panic if for whatever reason this fails. - let _ = serde_cbor::to_writer(&mut data, self); - encode_hex(&data) - } - - /// returns None if `s` is not a valid SDF string - pub fn import_string(s: &str) -> Option<Self> { - let bytes = decode_hex(s)?; - serde_cbor::from_reader(&bytes[..]).ok()? - } - pub fn good_random(rng: &mut impl Rng, config: &SceneConfig) -> Self { - let sdf = R3ToR::good_random(rng, config.sdf_max_depth); - let color_function = R3ToR3::good_random(rng, config.color_max_depth); + let sdf = R3ToR::good_random(rng, config.sdf_length); + let color_function = R3ToR3::good_random(rng, config.color_length); Scene { sdf, color_function, |