summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2023-01-26 13:38:12 -0500
committerpommicket <pommicket@gmail.com>2023-01-26 13:38:12 -0500
commit57dfb60fdc38d7b4815087d71781e2b326c6939b (patch)
treee4a50b9944d5af0d242c3ca4c1a05fd8172e435a
parentaaf92471dc68766faf401ce4bdbb2a271e68c964 (diff)
more documentation
-rw-r--r--gen_random/src/lib.rs6
-rw-r--r--src/main.rs62
-rw-r--r--src/sdf.rs163
-rw-r--r--src/win.rs7
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<Params: GenRandomParams>: Sized {
}
}
+/// generate a [Vec] of `len` random items.
pub fn gen_random_vec<P: GenRandomParams, T: GenRandom<P>>(rng: &mut impl Rng, len: usize) -> Vec<T> {
(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<P: GenRandomParams, T: GenRandom<P>>(len: usize) -> Vec<T> {
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<f32>;
type Mat4 = Matrix4<f32>;
type Rot3 = Rotation3<f32>;
+/// 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<SystemTime> {
- 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<Self, String> {
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<Self>;
}
@@ -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<Vec<u8>> {
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<R3ToR3>, Box<R3ToR3>),
+ /// f(x, y, z) = (f₁(x), f₂(y), f₃(z))
#[prob(4)]
#[only_if(params.max_depth >= 0)]
PerComponent(Box<RToR>, Box<RToR>, Box<RToR>),
+ /// a linear interpolation between two functions
#[prob(4)]
#[only_if(params.max_depth >= 0)]
Mix(Box<R3ToR3>, Box<R3ToR3>, 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<RnToRn>),
+ /// 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<RToR>, Box<RToR>),
+ /// subtract a constant
#[prob(0)]
Subtract(Constant),
+ /// a generic function applied to float
#[prob(2)]
- NToN(Box<RnToRn>)
+ NToN(Box<RnToRn>),
}
+/// 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<R3ToR3>, Box<R3ToR>, Box<RToR>),
+ /// linear interpolation between two functions
#[prob(4)]
#[only_if(params.max_depth >= 0)]
Mix(Box<R3ToR>, Box<R3ToR>, Constant),
+ /// sin(f(x))·cos(g(x))
#[prob(4)]
#[only_if(params.max_depth >= 0)]
SinCos(Box<R3ToR>, Box<R3ToR>),
+ /// "smooth" minimum of two functions
#[prob(2)]
#[only_if(params.max_depth >= 0)]
SmoothMin(Box<R3ToR>, Box<R3ToR>),
+ /// minimum of two functions
#[prob(2)]
#[only_if(params.max_depth >= 0)]
Min(Box<R3ToR>, Box<R3ToR>),
+ /// 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<SdfParams> + 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<SdfParams> + 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<SdfParams> + 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) {