summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2022-12-13 17:06:43 -0500
committerpommicket <pommicket@gmail.com>2022-12-13 17:06:43 -0500
commit3f699f3720ef8d912e509bc3785485b527419dc0 (patch)
treee598c780453ca2b7b4fc39e1f971a18213930708 /src
parent2e4f8e26edf0a3c367c4e12ce8e80e8797149df3 (diff)
sphere->cube!
Diffstat (limited to 'src')
-rw-r--r--src/main.rs144
-rw-r--r--src/sdf.rs206
-rw-r--r--src/win.rs92
3 files changed, 326 insertions, 116 deletions
diff --git a/src/main.rs b/src/main.rs
index 2ef9ff4..6fef680 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,21 +1,23 @@
/*
@TODO:
+- fix rotation
- fullscreen key
- options for:
- - max framerate
- - AA quality
- - # iterations, distance cutoff
+ - max framerate
+ - mouse sensitivity
+ - AA quality
+ - # iterations, distance cutoff
*/
extern crate nalgebra;
+pub mod sdf;
mod sdl;
pub mod win;
-pub mod sdf;
-use nalgebra::{Vector3, Matrix3, Rotation3, Matrix4};
+use nalgebra::{Matrix3, Matrix4, Rotation3, Vector3};
use std::time::Instant;
-
+
type Vec3 = Vector3<f32>;
type Mat3 = Matrix3<f32>;
type Mat4 = Matrix4<f32>;
@@ -24,7 +26,7 @@ type Rot3 = Rotation3<f32>;
struct View {
pos: Vec3,
yaw: f32,
- pitch: f32
+ pitch: f32,
}
impl Default for View {
@@ -32,7 +34,7 @@ impl Default for View {
Self {
pos: Vec3::zeros(),
yaw: 0.0,
- pitch: 0.0
+ pitch: 0.0,
}
}
}
@@ -42,35 +44,37 @@ impl View {
fn rotation(&self) -> Mat3 {
*Rot3::from_euler_angles(self.pitch, self.yaw, 0.0).matrix()
}
-
+
fn translation(&self) -> Mat4 {
Mat4::new_translation(&self.pos)
}
-
+
fn transform(&self) -> Mat4 {
self.translation() * self.rotation().to_homogeneous()
}
-
- fn inv_transform(&self) -> Mat4 {
- // this matrix should always be invertible
- self.transform().try_inverse().unwrap()
- }
-}
+}
fn try_main() -> Result<(), String> {
- let my_sdf = sdf::Sdf::sphere();
+ use sdf::{R3ToR, Constant};
+ let funciton = R3ToR::mix(R3ToR::sphere_f32(1.0), R3ToR::cube_f32(1.0),
+ Constant::Time(0.1, 0.0));
+ let my_sdf = sdf::Sdf::from_function(funciton);
let mut window = win::Window::new("AutoSDF", 1280, 720, true)
.map_err(|e| format!("Error creating window: {e}"))?;
-
+
let mut fshader_source = String::new();
- fshader_source.push_str("
+ fshader_source.push_str(
+ "
IN vec2 pos;
uniform mat4 u_transform;
-");
+uniform float u_time;
+",
+ );
my_sdf.to_glsl(&mut fshader_source);
- fshader_source.push_str("
+ fshader_source.push_str(
+ "
#define ITERATIONS 20
#define AA_X 1
#define AA_Y 1
@@ -117,11 +121,15 @@ void main() {
for (int m = 0; m < AA_X; m++) {
for (int n = 0; n < AA_Y; n++) {
vec2 aa_offset = vec2(float(m), float(n)) * aa_delta;
- vec3 absolute_pos = vec3(pos + aa_offset, -focal_length);
- vec3 delta = normalize(absolute_pos);
+ vec3 p = (u_transform * vec4(pos + aa_offset, -focal_length, 1.0)).xyz;
+ vec3 delta = normalize(p);
+ if (sdf(p) < 0.0) {
+ // looking inside object
+ o_color = vec4(1.0, 0.0, 1.0, 1.0);
+ return;
+ }
int i;
for (i = 0; i < ITERATIONS; i++) {
- vec3 p = (u_transform * vec4(absolute_pos, 1.0)).xyz;
float dist = sdf(p);
min_dist = min(min_dist, dist);
if (dist <= 0.01) {
@@ -130,18 +138,20 @@ void main() {
break;
}
if (dist > 100.0) break;//little optimization
- absolute_pos += dist * delta;
+ p += dist * delta;
}
}
}
final_color *= 1.0 / (AA_X * AA_Y);
o_color = vec4(final_color, 1.0);
-}");
-
+}",
+ );
+
println!("{fshader_source}");
-
- let program = window.create_program(
- "attribute vec2 v_pos;
+
+ let program = window
+ .create_program(
+ "attribute vec2 v_pos;
OUT vec2 pos;
uniform float u_aspect_ratio;
@@ -149,9 +159,10 @@ void main() {
pos = v_pos * vec2(u_aspect_ratio, 1.0);
gl_Position = vec4(v_pos, 0.0, 1.0);
}",
- &fshader_source
- ).map_err(|e| format!("Error compiling shader:\n{e}"))?;
-
+ &fshader_source,
+ )
+ .map_err(|e| format!("Error compiling shader:\n{e}"))?;
+
let mut buffer = window.create_buffer();
let data: &[[f32; 2]] = &[
[-1.0, -1.0],
@@ -164,15 +175,19 @@ void main() {
window.set_buffer_data(&mut buffer, data);
let mut array = window.create_vertex_array(buffer, &program);
window.array_attrib2f(&mut array, "v_pos", 0);
-
+
let mut view = View::default();
-
+
window.set_mouse_relative(true);
-
+
let mut frame_time = Instant::now();
let mut show_debug_info = false;
-
+ let mut total_time = 0.0;
+
'mainloop: loop {
+ let frame_dt = frame_time.elapsed().as_secs_f32();
+ frame_time = Instant::now();
+
while let Some(event) = window.next_event() {
use win::Event::*;
use win::Key::*;
@@ -180,29 +195,62 @@ void main() {
Quit | KeyDown(Escape) => break 'mainloop,
KeyDown(F1) => show_debug_info = !show_debug_info,
MouseMotion { xrel, yrel, .. } => {
- view.yaw += xrel as f32 * 0.01;
- view.pitch += yrel as f32 * 0.01;
- },
- _ => {},
+ view.yaw -= xrel as f32 * frame_dt;
+ view.pitch -= yrel as f32 * frame_dt;
+ }
+ _ => {}
}
}
- window.viewport_full_screen();
+ { // movement
+ let mut dx = 0.0;
+ let mut dy = 0.0;
+ let mut dz = 0.0;
+ use win::Key::{Left, Right, Up, Down, W, A, S, D, Q, E};
+ if window.is_key_down(W) || window.is_key_down(Up) {
+ dz -= 1.0;
+ }
+ if window.is_key_down(S) || window.is_key_down(Down) {
+ dz += 1.0;
+ }
+ if window.is_key_down(A) || window.is_key_down(Left) {
+ dx -= 1.0;
+ }
+ if window.is_key_down(D) || window.is_key_down(Right) {
+ dx += 1.0;
+ }
+ if window.is_key_down(Q) {
+ dy += 1.0;
+ }
+ if window.is_key_down(E) {
+ dy -= 1.0;
+ }
+ let motion = Vec3::new(dx, dy, dz);
+ if let Some(motion) = motion.try_normalize(0.001) {
+ let motion = motion * frame_dt;
+ let motion = view.rotation() * motion;
+ view.pos += motion;
+ }
+ }
+ window.viewport_full_screen();
+
window.clear_screen(win::ColorF32::BLACK);
window.use_program(&program);
window.uniform1f("u_aspect_ratio", window.aspect_ratio());
- window.uniform4x4f("u_transform", view.inv_transform().as_slice());
-
+ window.uniform1f("u_time", total_time);
+ window.uniform4x4f("u_transform", view.transform().as_slice());
+
window.draw_array(&array);
-
+
window.swap();
if show_debug_info {
- println!("frame time = {:?}",frame_time.elapsed());
- frame_time = Instant::now();
+ println!("frame time = {:?}ms", frame_dt * 1000.0);
}
+
+ total_time += frame_dt;
}
-
+
Ok(())
}
diff --git a/src/sdf.rs b/src/sdf.rs
index f60eae5..ba483a7 100644
--- a/src/sdf.rs
+++ b/src/sdf.rs
@@ -7,76 +7,210 @@ macro_rules! write_str {
($( $arg:tt )*) => { write!($($arg)*).unwrap() }
}
-enum R3ToR3 {
+/// these are constant across 3D space, not across time/user input/etc.
+pub enum Constant {
+ F32(f32),
+ Time(f32, f32),
+}
+
+impl From<f32> for Constant {
+ fn from(x: f32) -> Self {
+ Self::F32(x)
+ }
+}
+
+impl Constant {
+ fn to_glsl(&self) -> String {
+ use Constant::*;
+ match self {
+ F32(x) => format!("{x:.1}"),
+ Time(x, y) => format!("({x:.1} * u_time + {y:.1})"),
+ }
+ }
+}
+
+pub struct Constant3(Constant, Constant, Constant);
+
+impl Constant3 {
+ fn to_glsl(&self) -> String {
+ format!(
+ "vec3({}, {}, {})",
+ self.0.to_glsl(),
+ self.1.to_glsl(),
+ self.2.to_glsl()
+ )
+ }
+}
+
+pub enum R3ToR3 {
Identity,
- Translate([f32; 3])
+ Translate(Constant3),
}
-enum RToR {
+pub enum RToR {
Identity,
- Add(f32)
+ Add(Constant),
+}
+
+pub enum R3ToR {
+ Sphere(Constant),
+ Cube(Constant),
+ PrePost(Box<R3ToR3>, Box<R3ToR>, Box<RToR>),
+ Mix(Box<R3ToR>, Box<R3ToR>, Constant),
+ SmoothMin(Box<R3ToR>, Box<R3ToR>),
+ Min(Box<R3ToR>, Box<R3ToR>),
+}
+
+impl R3ToR {
+ pub fn sphere_f32(r: f32) -> Self {
+ Self::Sphere(r.into())
+ }
+
+ pub fn cube_f32(r: f32) -> Self {
+ Self::Cube(r.into())
+ }
+
+ pub fn mix(a: Self, b: Self, t: Constant) -> Self {
+ Self::Mix(Box::new(a), Box::new(b), t)
+ }
+
+ pub fn mix_f32(a: Self, b: Self, t: f32) -> Self {
+ Self::mix(a, b, t.into())
+ }
+}
+
+struct VarCounter {
+ idx: u32,
+}
+
+impl VarCounter {
+ fn new() -> Self {
+ Self { idx: 0 }
+ }
+
+ fn prev(&self) -> u32 {
+ assert!(self.idx != 0);
+ self.idx - 1
+ }
+
+ fn next(&mut self) -> u32 {
+ let ret = self.idx;
+ self.idx += 1;
+ ret
+ }
}
pub struct Sdf {
- pre: R3ToR3,
- post: RToR
+ distance_function: R3ToR,
+}
+
+trait Function {
+ /// treats `v<input>` as the input, and puts the output in `v<return value>`.
+ fn to_glsl(&self, input: u32, code: &mut String, var: &mut VarCounter) -> u32;
}
-impl RToR {
- /// treats `v<*initial value of var_idx>` as the input, and puts the output in `v<final value of *var_idx>`.
- fn to_glsl(&self, code: &mut String, var_idx: &mut u32) {
+impl Function for RToR {
+ fn to_glsl(&self, input: u32, code: &mut String, var: &mut VarCounter) -> u32 {
use RToR::*;
- let input = *var_idx;
-
+
match self {
- Identity => {}, // no code
+ Identity => return input,
Add(x) => {
- *var_idx += 1;
- let output = *var_idx;
- write_str!(code, "float v{output} = v{input} + {x};\n");
+ write_str!(
+ code,
+ "float v{} = v{input} + {};\n",
+ var.next(),
+ x.to_glsl()
+ );
}
}
+ var.prev()
}
}
-impl R3ToR3 {
- /// treats `v<*initial value of var_idx>` as the input, and puts the output in `v<final value of *var_idx>`.
- fn to_glsl(&self, code: &mut String, var_idx: &mut u32) {
+impl Function for R3ToR3 {
+ fn to_glsl(&self, input: u32, code: &mut String, var: &mut VarCounter) -> u32 {
use R3ToR3::*;
- let input = *var_idx;
-
+
+ match self {
+ Identity => return input,
+ Translate(by) => {
+ write_str!(
+ code,
+ "vec3 v{} = v{input} + {};\n",
+ var.next(),
+ by.to_glsl()
+ );
+ }
+ }
+
+ var.prev()
+ }
+}
+
+impl Function for R3ToR {
+ fn to_glsl(&self, input: u32, code: &mut String, var: &mut VarCounter) -> u32 {
+ use R3ToR::*;
match self {
- Identity => {}, // no code
- Translate([x, y, z]) => {
- *var_idx += 1;
- let output = *var_idx;
- write_str!(code, "vec3 v{output} = v{input} + vec3({x}, {y}, {z});\n");
- },
+ // thanks to https://iquilezles.org/articles/distfunctions/ for
+ // these SDFs.
+ Sphere(r) => {
+ let r = r.to_glsl();
+ write_str!(code, "float v{} = length(v{input}) - {r};\n", var.next());
+ }
+ Cube(r) => {
+ let r = r.to_glsl();
+ let q = var.next();
+ write_str!(code, "vec3 v{q} = abs(v{input}) - {r};\n");
+ write_str!(
+ code,
+ "float v{} = length(max(v{q},0.0)) + min(max(v{q}.x,max(v{q}.y,v{q}.z)),0.0);\n",
+ var.next()
+ )
+ }
+ Mix(a, b, t) => {
+ let t = t.to_glsl();
+ let a_output = a.to_glsl(input, code, var);
+ let b_output = b.to_glsl(input, code, var);
+ write_str!(
+ code,
+ "float v{} = mix(v{a_output}, v{b_output}, clamp({t}, 0.0, 1.0));\n",
+ var.next()
+ );
+ }
+ _ => todo!(),
}
+
+ var.prev()
}
}
impl Sdf {
/// test sphere
- pub fn sphere() -> Self {
+ pub fn sphere(r: f32) -> Self {
Self {
- pre: R3ToR3::Identity,
- post: RToR::Identity
+ distance_function: R3ToR::Sphere(Constant::F32(r)),
}
}
+ pub fn from_function(distance_function: R3ToR) -> Self {
+ Self { distance_function }
+ }
+
/// appends some glsl code including a function `float sdf(vec3 p) { ... }`
pub fn to_glsl(&self, code: &mut String) {
code.push_str("float sdf(vec3 p) {\n");
// don't start out right next to the origin, since weird stuff might be happening there
let origin_dist: f32 = 3.0;
- write_str!(code, "vec3 v0 = p - vec3(0,0,-{}.);\n", origin_dist);
- let mut var_idx = 0;
- self.pre.to_glsl(code, &mut var_idx);
- write_str!(code, "float v{} = length(v{}) - 1.0;\n", var_idx + 1, var_idx);
- var_idx += 1;
- self.post.to_glsl(code, &mut var_idx);
- write_str!(code, "return v{var_idx};\n");
+ let mut var = VarCounter::new();
+ write_str!(
+ code,
+ "vec3 v{} = p - vec3(0,0,-{}.);\n",
+ var.next(),
+ origin_dist
+ );
+ let output = self.distance_function.to_glsl(var.prev(), code, &mut var);
+ write_str!(code, "return v{output};\n");
code.push('}');
}
}
diff --git a/src/win.rs b/src/win.rs
index d54921e..4badc76 100644
--- a/src/win.rs
+++ b/src/win.rs
@@ -2,11 +2,11 @@
// this is because OpenGL is not thread safe.
use crate::sdl;
use gl::types::{GLchar, GLenum, GLint, GLsizei, GLuint};
+use mem::size_of;
#[allow(unused_imports)]
use std::ffi::{c_char, c_int, c_uint, c_void, CStr, CString};
use std::sync::Mutex;
use std::{fmt, mem};
-use mem::size_of;
pub type AudioCallback = fn(sample_rate: u32, samples: &mut [f32]);
@@ -79,7 +79,7 @@ pub enum Key {
F9,
F10,
F11,
- F12
+ F12,
}
impl Key {
@@ -212,7 +212,12 @@ pub enum Event {
Quit,
KeyDown(Key),
KeyUp(Key),
- MouseMotion { x: i32, y: i32, xrel: i32, yrel: i32 },
+ MouseMotion {
+ x: i32,
+ y: i32,
+ xrel: i32,
+ yrel: i32,
+ },
}
pub fn display_error_message(message: &str) {
@@ -308,11 +313,11 @@ impl From<u32> for ColorU8 {
impl ColorF32 {
pub const BLACK: Self = Self::rgb(0.0, 0.0, 0.0);
-
+
pub const fn rgb(r: f32, g: f32, b: f32) -> Self {
ColorF32 { r, g, b, a: 1.0 }
}
-
+
pub const fn rgba(r: f32, g: f32, b: f32, a: f32) -> Self {
ColorF32 { r, g, b, a }
}
@@ -342,25 +347,27 @@ impl Shader {
{
//set source
// @TODO(eventually): support for older versions of GLSL
- let header = if r#type == gl::FRAGMENT_SHADER { "#version 130
+ let header = if r#type == gl::FRAGMENT_SHADER {
+ "#version 130
#define IN in
#define OUT out
#define gl_FragColor o_color
out vec4 o_color;
#line 1
-" } else {
-"#version 130
+"
+ } else {
+ "#version 130
#define IN in
#define OUT out
#define ATTRIBUTE in
#line 1
"
-};
+ };
let hdrptr = header.as_bytes().as_ptr() as *const GLchar;
let srcptr = source.as_bytes().as_ptr() as *const GLchar;
let sources = [hdrptr, srcptr];
let lengths = [header.len() as GLint, source.len() as GLint];
-
+
let sources_ptr = &sources[0] as *const *const GLchar;
let lengths_ptr = &lengths[0] as *const GLint;
@@ -427,7 +434,7 @@ impl Program {
eprintln!("{}", String::from_utf8_lossy(&log[..len as usize]));
}
}
-
+
{
let mut status: GLint = 0;
unsafe { gl::GetProgramiv(id, gl::LINK_STATUS, (&mut status) as _) };
@@ -435,7 +442,7 @@ impl Program {
return Err(format!("failed to link"));
}
}
-
+
Ok(Self { id })
}
}
@@ -503,7 +510,11 @@ impl VertexArray {
unsafe { gl::GenVertexArrays(1, &mut id as *mut GLuint) };
- Self { id, buffer, program: program.id }
+ Self {
+ id,
+ buffer,
+ program: program.id,
+ }
}
fn bind(&self) {
@@ -515,16 +526,23 @@ impl VertexArray {
let cstr = cstring.as_ptr() as *const GLchar;
let loc = unsafe { gl::GetAttribLocation(self.program, cstr) };
let Ok(loc) = loc.try_into() else { return false };
-
+
if offset + usize::from(n) * size_of::<f32>() > self.buffer.stride as usize {
// offset too large
return false;
}
-
+
self.bind();
self.buffer.bind();
unsafe {
- gl::VertexAttribPointer(loc, n.into(), gl::FLOAT, 0, self.buffer.stride as _, offset as _)
+ gl::VertexAttribPointer(
+ loc,
+ n.into(),
+ gl::FLOAT,
+ 0,
+ self.buffer.stride as _,
+ offset as _,
+ )
};
unsafe { gl::EnableVertexAttribArray(loc) };
true
@@ -662,9 +680,11 @@ impl Window {
pub fn show(&mut self) {
unsafe { sdl::show_window(self.sdlwin) };
}
-
+
pub fn set_mouse_relative(&mut self, relative: bool) {
- unsafe { sdl::set_relative_mouse_mode(relative); }
+ unsafe {
+ sdl::set_relative_mouse_mode(relative);
+ }
}
pub fn create_program(
@@ -689,18 +709,24 @@ impl Window {
VertexArray::new(buffer, program)
}
- fn array_attribnf(&mut self, array: &mut VertexArray, n: u8, name: &str, offset: usize) -> bool {
+ fn array_attribnf(
+ &mut self,
+ array: &mut VertexArray,
+ n: u8,
+ name: &str,
+ offset: usize,
+ ) -> bool {
array.attribnf(n, name, offset)
}
-
+
pub fn array_attrib2f(&mut self, array: &mut VertexArray, name: &str, offset: usize) -> bool {
self.array_attribnf(array, 2, name, offset)
}
-
+
pub fn array_attrib3f(&mut self, array: &mut VertexArray, name: &str, offset: usize) -> bool {
self.array_attribnf(array, 3, name, offset)
}
-
+
pub fn array_attrib4f(&mut self, array: &mut VertexArray, name: &str, offset: usize) -> bool {
self.array_attribnf(array, 4, name, offset)
}
@@ -711,7 +737,7 @@ impl Window {
unsafe { sdl::get_window_size(self.sdlwin, &mut x, &mut y) };
(x, y)
}
-
+
pub fn aspect_ratio(&self) -> f32 {
let (w, h) = self.size();
return w as f32 / h as f32;
@@ -720,7 +746,7 @@ impl Window {
pub fn viewport(&mut self, x: i32, y: i32, w: i32, h: i32) {
unsafe { gl::Viewport(x, y, w, h) };
}
-
+
pub fn viewport_full_screen(&mut self) {
let (w, h) = self.size();
self.viewport(0, 0, w, h);
@@ -741,16 +767,16 @@ impl Window {
return Some(Event::KeyUp(k));
}
}
- },
+ }
sdl::SDL_MOUSEMOTION => {
let motion = unsafe { sdl.motion };
return Some(Event::MouseMotion {
x: motion.x,
y: motion.y,
xrel: motion.xrel,
- yrel: motion.yrel
+ yrel: motion.yrel,
});
- },
+ }
_ => {}
}
}
@@ -768,7 +794,10 @@ impl Window {
unsafe {
gl::GenTextures(1, (&mut id) as *mut GLuint);
}
- Texture { id, params: params.clone() }
+ Texture {
+ id,
+ params: params.clone(),
+ }
}
pub fn set_texture_data(
@@ -869,7 +898,7 @@ impl Window {
self.used_program = program.id;
}
}
-
+
fn get_uniform_location(&self, name: &str) -> Option<GLint> {
if self.used_program == 0 {
return None;
@@ -904,7 +933,7 @@ impl Window {
gl::Uniform2i(loc, x, y);
}
}
-
+
pub fn uniform3i(&mut self, name: &str, x: i32, y: i32, z: i32) {
let loc = self.get_uniform_location(name).unwrap_or(-1);
unsafe {
@@ -932,7 +961,7 @@ impl Window {
gl::Uniform2f(loc, x, y);
}
}
-
+
pub fn uniform3f(&mut self, name: &str, x: f32, y: f32, z: f32) {
let loc = self.get_uniform_location(name).unwrap_or(-1);
unsafe {
@@ -963,7 +992,6 @@ impl Window {
}
}
-
pub fn uniform_texture(&mut self, name: &str, slot: u32) {
self.uniform1i(name, slot as i32);
}