summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2022-12-17 23:30:25 -0500
committerpommicket <pommicket@gmail.com>2022-12-17 23:30:25 -0500
commit86182eba5bf613164d634b37afe1bd9ae5042b46 (patch)
treee74a8a7a7465ce871faf81e7f18bc91a6ef8b672
parent298f893666e5626bacb6fc242f1059a5d1664294 (diff)
settings!
-rw-r--r--.gitignore1
-rw-r--r--README.md21
-rw-r--r--settings.txt23
-rw-r--r--src/fshader_main.glsl11
-rw-r--r--src/main.rs105
-rw-r--r--src/sdf.rs191
6 files changed, 249 insertions, 103 deletions
diff --git a/.gitignore b/.gitignore
index ba07c0f..af29871 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,4 @@ tags
*.out
*.dll
*.lib
+scenes.txt
diff --git a/README.md b/README.md
index 8c31f36..b37bd89 100644
--- a/README.md
+++ b/README.md
@@ -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(())
diff --git a/src/sdf.rs b/src/sdf.rs
index 025373e..87a5879 100644
--- a/src/sdf.rs
+++ b/src/sdf.rs
@@ -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,