aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoreug-vs <eugene@eug-vs.xyz>2021-10-29 22:18:43 +0300
committereug-vs <eugene@eug-vs.xyz>2021-10-29 22:37:55 +0300
commit61c757f2499d79efe4066ca2d8c1ff7827604543 (patch)
tree3593de9b7eba2c390cddc546d0013ef7ac81677d
parentb79ea97978a38fcb2ac7c776a37531d6babcb84e (diff)
downloadpistol-61c757f2499d79efe4066ca2d8c1ff7827604543.tar.gz
refactor!: separate rendering functionality
-rw-r--r--src/buffer.rs16
-rw-r--r--src/camera.rs200
-rw-r--r--src/main.rs108
-rw-r--r--src/renderer.rs110
-rw-r--r--src/sdf.rs60
5 files changed, 276 insertions, 218 deletions
diff --git a/src/buffer.rs b/src/buffer.rs
new file mode 100644
index 0000000..a2c5708
--- /dev/null
+++ b/src/buffer.rs
@@ -0,0 +1,16 @@
+#[derive(Debug, Clone)]
+pub struct Buffer {
+ pub width: f32,
+ pub height: f32,
+ pub palette: Vec<char>,
+}
+
+impl Buffer {
+ pub fn from_height(height: f32, aspect_ratio: f32) -> Self {
+ Self {
+ height,
+ width: height * aspect_ratio,
+ palette: "$@B%8&WM#oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1{}[]?-_+~<>i!lI;:,\"^`'. ".chars().collect(),
+ }
+ }
+}
diff --git a/src/camera.rs b/src/camera.rs
index 8409ea5..84d8446 100644
--- a/src/camera.rs
+++ b/src/camera.rs
@@ -1,196 +1,58 @@
-use cgmath::Matrix3;
-use cgmath::Vector3;
+use cgmath::{Vector3, Matrix3};
use cgmath::prelude::*;
-use ncurses::addch;
-use ncurses::addstr;
-use std::f32;
-use std::f32::consts::PI;
-use std::time::Instant;
-
type Vector = Vector3<f32>;
-pub const HEIGHT: i32 = 50;
-pub const WIDTH: i32 = HEIGHT * 3;
+// The physical screen that camera projects onto
+#[derive(Debug, Copy, Clone)]
+pub struct Screen {
+ pub width: f32,
+ pub height: f32
+}
-#[derive(Debug)]
+#[derive(Debug, Copy, Clone)]
pub struct Camera {
- pub time: f32,
pub position: Vector,
pub direction: Vector,
pub up: Vector,
- pub light: Vector,
- pub angle: f32,
pub distance: f32,
- pub brightness: f32,
- pub aspect_ratio: f32,
pub speed: f32,
pub turn_rate: f32,
- pub width: f32,
- pub height: f32,
- pub palette: Vec<char>,
-}
-
-fn softmin(left: f32, right: f32, k: f32) -> f32 {
- // return left.min(right);
- let h = (k-(left-right).abs()).max(0.0) / k;
- return left.min(right) - h*h*k*(1.0/4.0);
+ pub screen: Screen,
}
-fn sd_sphere(point: Vector, center: Vector, radius: f32) -> f32 {
- (point - center).magnitude() - radius
-}
-
-fn sd_box(point: Vector, center: Vector, size: Vector) -> f32 {
- let diff = center - point;
- let q = diff.map(|n| n.abs()) - size / 2.0;
- return q.map(|n| n.max(0.0)).magnitude() + (q.y.max(q.z).max(q.x)).min(0.0)
-}
+const ASPECT_RATIO: f32 = 2.0 / 3.0;
impl Camera {
- pub fn sd_gear(&self, point: Vector, center: Vector, radius: f32, thickness: f32, turn_rate: f32) -> f32 {
- let mut dist: f32;
-
- let thickness_over_2 = thickness / 2.0;
- let thickness_over_4 = thickness / 4.0;
-
- // Ring
- {
- let cylinder_dist = (Vector::new(0.0, point.y, point.z) - center).magnitude() - (radius - thickness_over_4);
- dist = cylinder_dist.abs() - thickness_over_2; // Make cylinder hollow
+ pub fn new(position: Vector, look_at: Vector, angle: f32, distance: f32) -> Self {
+ let width = distance * 2.0 * (angle / 2.0).tan();
+ let height = width * ASPECT_RATIO;
+
+ Self {
+ position,
+ direction: (look_at - position).normalize(),
+ up: Vector::unit_z(),
+ distance,
+ screen: Screen { width, height },
+ speed: 0.5,
+ turn_rate: 60.0
}
-
- // Teeth
- {
- let sector_angle: f32 = 2.0 * PI / 12.0;
-
- // Account for rotation with time
- let angle = sector_angle * self.time / turn_rate;
- let rotated_point = Vector::new(
- point.x,
- point.y * angle.cos() - point.z * angle.sin(),
- point.y * angle.sin() + point.z * angle.cos()
- );
-
- // Map all space to the first sector
- let point_angle = (rotated_point.z / rotated_point.y).atan();
- let angle2 = -sector_angle * (point_angle / sector_angle).round();
-
- let mapped_point = Vector::new(
- rotated_point.x,
- (rotated_point.y * angle2.cos() - rotated_point.z * angle2.sin()).abs(),
- rotated_point.y * angle2.sin() + rotated_point.z * angle2.cos()
- );
-
- let center = Vector { x: 0.0, y: radius + thickness_over_2, z: 0.0 };
- let size = Vector::new(thickness, thickness * 2.0, thickness);
- // Make teeth smooth by subtracting some amount
- dist = dist.min(sd_box(mapped_point, center, size) - thickness_over_4);
- }
-
- // Take a slice
- dist = dist.max(point.x.abs() - thickness_over_2);
-
- return dist;
- }
- pub fn sdf(&self, point: Vector) -> f32 {
- self.sd_gear(point, Vector::zero(), 3.0, 0.6, 10.0)
}
-
- pub fn render(& mut self) {
+ pub fn get_screen_iterator(self) -> (Vector, Vector, Vector) {
// Linear transormation operator for calculating screen position
// Assumes "initial" screen is perpendicular to OX
// and it's bottom edge is parallel to OY
let operator = Matrix3::from_cols(
self.direction * self.distance,
- self.direction.cross(self.up) * self.width,
- -self.up * self.height,
+ self.direction.cross(self.up) * self.screen.width,
+ -self.up * self.screen.height,
);
- let mut ray_dir = operator * Vector::new(1.0, -0.5, -0.5); // Corner
- let step_v = operator * Vector3::unit_z() / HEIGHT as f32;
- let step_h = operator * Vector3::unit_y() / WIDTH as f32;
-
- for _i in 0..HEIGHT as usize {
- ray_dir += step_v;
- let mut row = "\n".to_string();
- for _j in 0..WIDTH as usize {
- ray_dir += step_h;
-
- let collision = self.ray_marching(self.position, ray_dir);
+ let corner_dir = operator * Vector::new(1.0, -0.5, -0.5); // Corner
+ let step_v = operator * Vector::unit_z();
+ let step_h = operator * Vector::unit_y();
- let brightness = match collision {
- Some(point) => self.light_point(point),
- None => 0.0
- };
-
- row.push(self.palette[((1.0 - brightness) * (self.palette.len() - 1) as f32) as usize]);
- }
- ray_dir -= step_h * WIDTH as f32;
- addstr(&row);
- }
- }
-
- pub fn normal(&self, point: Vector) -> Vector {
- let d = 0.001;
-
- let dx = Vector::unit_x() * d;
- let dy = Vector::unit_y() * d;
- let dz = Vector::unit_z() * d;
-
- let sdf = self.sdf(point);
-
- return (Vector {
- x: (self.sdf(point + dx) - sdf),
- y: (self.sdf(point + dy) - sdf),
- z: (self.sdf(point + dz) - sdf),
- } / d).normalize()
- }
-
- pub fn ray_marching(&self, origin: Vector, direction: Vector) -> Option<Vector> {
- let threshold = 0.1;
-
- let ray = direction.normalize();
- let mut point = origin;
- let mut dist = 0.0;
- let mut count = 0;
-
- while dist < self.brightness && count < 10 {
- count += 1;
- dist = self.sdf(point);
- if dist.abs() < threshold {
- return Some(point);
- }
- point += ray * dist;
- }
-
- return None
- }
-
- pub fn light_point(&self, point: Vector) -> f32 {
- let ambient = 0.1;
- return ambient + (1.0 - ambient) * (self.diffuse_lighting(point) * 0.7 + self.specular_lighting(point) * 0.3)
- }
-
- pub fn diffuse_lighting(&self, point: Vector) -> f32 {
- let mut res: f32 = 1.0;
- let mut t = 0.1;
- let k = 4.0;
-
- while t < 1.0 {
- let h = self.sdf(point - self.light * t);
- if h < 0.001 {
- return 0.00
- }
- res = res.min(k * h / t);
- t += h;
- }
-
- return res
- }
-
- pub fn specular_lighting(&self, point: Vector) -> f32 {
- let normal = self.normal(point);
- let dot = -(normal.dot(self.light));
- return dot.min(1.0).max(0.0)
+ // TODO: return an actual iterator
+ return (corner_dir, step_h, step_v)
}
}
+
diff --git a/src/main.rs b/src/main.rs
index da16e28..694a843 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,90 +1,100 @@
extern crate ncurses;
mod camera;
+mod buffer;
+mod renderer;
+mod sdf;
+
use std::{f32::consts::PI, time::Instant};
-use cgmath::{Angle, InnerSpace, Matrix3, Rad, Vector3};
+use cgmath::{Angle, Matrix3, Rad, Vector3, Zero};
use ncurses::*;
-use crate::camera::{Camera, WIDTH, HEIGHT};
+use camera::Camera;
+use buffer::Buffer;
+use renderer::Renderer;
+use sdf::sd_gear;
+
fn main() {
- let mut cam = Camera {
- position: Vector3::new(-5.0, 0.0, 2.0),
- direction: Vector3::unit_x(),
- up: Vector3::unit_z(),
- light: Vector3 { x: 1.0, y: 1.0, z: -1.0 }.normalize(),
- angle: PI / 2.0,
- distance: 1.0,
- aspect_ratio: 2.0 * HEIGHT as f32 / WIDTH as f32,
- brightness: 15.0,
- time: 0.0,
- speed: 0.5,
- turn_rate: 30.0,
- width: 2.0,
- height: 4.0 * HEIGHT as f32 / WIDTH as f32,
- palette: "$@B%8&WM#oahkbdpqwmZO0QLCJUYXzcvunxrjft/\\|()1{}[]?-_+~<>i!lI;:,\"^`'. ".chars().collect(),
+ let mut renderer = Renderer {
+ buffer:Buffer::from_height(50.0, 3.0),
+ camera: Camera::new(
+ Vector3::new(-4.0, 0.0, 0.0),
+ Vector3::zero(),
+ PI / 2.0,
+ 1.0
+ )
+ };
+
+ // This closure will later be built
+ // by parsing a JSON scene
+ let sdf_global = |point: Vector3<f32>, time: f32| -> f32 {
+ sd_gear(point, time, Vector3::zero(), 2.0, 0.4, 30.0)
};
initscr();
+ let mut time = 0.0;
+
while true {
clear();
flushinp();
+ time += 1.0;
+
+ let sdf = |point: Vector3<f32>| -> f32 {
+ sdf_global(point, time)
+ };
+
// Render
- cam.time += 1.0;
let timestamp = Instant::now();
- cam.render();
- addstr(&format!("\nRendered in {:?} ({:.0} FPS)\n", timestamp.elapsed(), 1.0 / timestamp.elapsed().as_secs_f64()));
- addstr(&format!("\nTime: {:?}\n", cam.time));
- addstr(&format!("Camera: {:?}\n", cam.position));
- addstr(&format!("Facing: {:?}, Up: {:?}\n", cam.direction, cam.up));
- addstr(&format!("Light: {:?}\n", cam.light));
+ renderer.render(&sdf);
+ addstr(&format!("\nRendered in {:?} ({:.1} FPS)\n", timestamp.elapsed(), 1.0 / timestamp.elapsed().as_secs_f64()));
+ addstr(&format!("Camera: {:?}\n", renderer.camera.position));
+ addstr(&format!("Facing: {:?}, Up: {:?}\n", renderer.camera.direction, renderer.camera.up));
+
refresh();
// Handle input
+ // TODO: move all bullshit below to a separate file
let char = getch();
addstr(&format!("\nPressed: {:?}\n", char));
refresh();
if char == 107 { // k to move forward
- cam.position += cam.direction * cam.speed;
+ renderer.camera.position += renderer.camera.direction * renderer.camera.speed;
} else if char == 106 { // j to move backward
- cam.position -= cam.direction * cam.speed;
+ renderer.camera.position -= renderer.camera.direction * renderer.camera.speed;
} else if char == 72 { // H to move left
- cam.position += Matrix3::from_axis_angle(cam.up, Rad::turn_div_4()) * cam.direction * cam.speed;
+ renderer.camera.position += Matrix3::from_axis_angle(renderer.camera.up, Rad::turn_div_4()) * renderer.camera.direction * renderer.camera.speed;
} else if char == 76 { // L to move right
- cam.position -= Matrix3::from_axis_angle(cam.up, Rad::turn_div_4()) * cam.direction * cam.speed;
+ renderer.camera.position -= Matrix3::from_axis_angle(renderer.camera.up, Rad::turn_div_4()) * renderer.camera.direction * renderer.camera.speed;
} else if char == 104 { // h to rotate left
- let rotation = Matrix3::from_angle_z(Rad::full_turn() / cam.turn_rate);
- cam.direction = rotation * cam.direction;
- cam.up = rotation * cam.up;
+ let rotation = Matrix3::from_angle_z(Rad::full_turn() / renderer.camera.turn_rate);
+ renderer.camera.direction = rotation * renderer.camera.direction;
+ renderer.camera.up = rotation * renderer.camera.up;
} else if char == 108 { // l to rotate right
- let rotation = Matrix3::from_angle_z(-Rad::full_turn() / cam.turn_rate);
- cam.direction = rotation * cam.direction;
- cam.up = rotation * cam.up;
+ let rotation = Matrix3::from_angle_z(-Rad::full_turn() / renderer.camera.turn_rate);
+ renderer.camera.direction = rotation * renderer.camera.direction;
+ renderer.camera.up = rotation * renderer.camera.up;
} else if char == 75 { // K to rotate up
- let axis = cam.up.cross(cam.direction);
- let angle = -Rad::full_turn() / cam.turn_rate;
+ let axis = renderer.camera.up.cross(renderer.camera.direction);
+ let angle = -Rad::full_turn() / renderer.camera.turn_rate;
let rotation = Matrix3::from_axis_angle(axis, angle);
- cam.up = rotation * cam.up;
- cam.direction = rotation * cam.direction;
+ renderer.camera.up = rotation * renderer.camera.up;
+ renderer.camera.direction = rotation * renderer.camera.direction;
} else if char == 74 { // J to rotate down
- let axis = cam.up.cross(cam.direction);
- let angle = Rad::full_turn() / cam.turn_rate;
+ let axis = renderer.camera.up.cross(renderer.camera.direction);
+ let angle = Rad::full_turn() / renderer.camera.turn_rate;
let rotation = Matrix3::from_axis_angle(axis, angle);
- cam.up = rotation * cam.up;
- cam.direction = rotation * cam.direction;
+ renderer.camera.up = rotation * renderer.camera.up;
+ renderer.camera.direction = rotation * renderer.camera.direction;
} else if char == 117 { // u to move up along Z
- cam.position += Vector3::unit_z() * cam.speed;
+ renderer.camera.position += Vector3::unit_z() * renderer.camera.speed;
} else if char == 100 { // d to move down along Z
- cam.position -= Vector3::unit_z() * cam.speed;
+ renderer.camera.position -= Vector3::unit_z() * renderer.camera.speed;
} else if char == 70 { // F to reverse camera direction
- cam.direction = -cam.direction;
- } else if char == 101 { // e to change lights
- cam.light = Matrix3::from_angle_z(Rad::turn_div_2() / cam.turn_rate) * cam.light;
- } else if char == 69 { // E to change lights vertically
- cam.light = Matrix3::from_angle_y(Rad::turn_div_2() / cam.turn_rate) * cam.light;
+ renderer.camera.direction = -renderer.camera.direction;
}
}
diff --git a/src/renderer.rs b/src/renderer.rs
new file mode 100644
index 0000000..468f4ff
--- /dev/null
+++ b/src/renderer.rs
@@ -0,0 +1,110 @@
+use cgmath::Vector3;
+use cgmath::prelude::*;
+use ncurses::addstr;
+use std::f32;
+
+use crate::Buffer;
+use crate::Camera;
+
+type Vector = Vector3<f32>;
+
+pub struct Renderer {
+ pub camera: Camera,
+ pub buffer: Buffer,
+}
+
+impl Renderer {
+ pub fn render(&self, sdf: &dyn Fn(Vector) -> f32) {
+ let (mut ray_dir, mut step_h, mut step_v) = self.camera.get_screen_iterator();
+
+ step_v /= self.buffer.height;
+ step_h /= self.buffer.width;
+
+ for _i in 0..self.buffer.height as usize {
+ ray_dir += step_v;
+ let mut row = "\n".to_string();
+ for _j in 0..self.buffer.width as usize {
+ ray_dir += step_h;
+
+ let collision = Self::ray_march(self.camera.position, ray_dir, sdf);
+
+ let brightness = match collision {
+ Some(point) => Self::light_point(point, sdf),
+ None => 0.0
+ };
+
+ row.push(self.buffer.palette[((1.0 - brightness) * (self.buffer.palette.len() - 1) as f32) as usize]);
+ }
+ ray_dir -= step_h * self.buffer.width;
+ addstr(&row);
+ }
+ }
+
+ pub fn ray_march(origin: Vector, direction: Vector, sdf: &dyn Fn(Vector) -> f32) -> Option<Vector> {
+ let threshold = 0.1;
+
+ let ray = direction.normalize();
+ let mut point = origin;
+ let mut dist = 0.0;
+ let mut count = 0;
+
+ while dist < 8.0 && count < 10 {
+ count += 1;
+ dist = sdf(point);
+ if dist.abs() < threshold {
+ return Some(point);
+ }
+ point += ray * dist;
+ }
+
+ return None
+ }
+
+ pub fn light_point(point: Vector, sdf: &dyn Fn(Vector) -> f32) -> f32 {
+ let light = Vector::new(1.0, 1.0, -1.0);
+ let ambient = 0.1;
+ return ambient + (1.0 - ambient) * (
+ Self::diffuse_lighting(point, light, sdf) * 0.7 +
+ Self::specular_lighting(point, light, sdf) * 0.3
+ )
+ }
+
+ pub fn diffuse_lighting(point: Vector, light: Vector, sdf: &dyn Fn(Vector) -> f32) -> f32 {
+ let mut res: f32 = 1.0;
+ let mut t = 0.1;
+ let k = 4.0;
+
+ while t < 1.0 {
+ let h = sdf(point - light * t);
+ if h < 0.001 {
+ return 0.00
+ }
+ res = res.min(k * h / t);
+ t += h;
+ }
+
+ return res
+ }
+
+ pub fn specular_lighting(point: Vector, light: Vector, sdf: &dyn Fn(Vector) -> f32) -> f32 {
+ let normal = Self::normal(point, sdf);
+ let dot = -(normal.dot(light));
+ return dot.min(1.0).max(0.0)
+ }
+
+ pub fn normal(point: Vector, sdf: &dyn Fn(Vector) -> f32) -> Vector {
+ let d = 0.001;
+
+ let dx = Vector::unit_x() * d;
+ let dy = Vector::unit_y() * d;
+ let dz = Vector::unit_z() * d;
+
+ let dist = sdf(point);
+
+ return (Vector {
+ x: (sdf(point + dx) - dist),
+ y: (sdf(point + dy) - dist),
+ z: (sdf(point + dz) - dist),
+ } / d).normalize()
+ }
+}
diff --git a/src/sdf.rs b/src/sdf.rs
new file mode 100644
index 0000000..00a4a11
--- /dev/null
+++ b/src/sdf.rs
@@ -0,0 +1,60 @@
+use std::f32::consts::PI;
+use cgmath::Vector3;
+use cgmath::prelude::*;
+
+type Vector = Vector3<f32>;
+
+pub fn sd_sphere(point: Vector, center: Vector, radius: f32) -> f32 {
+ (point - center).magnitude() - radius
+}
+
+pub fn sd_box(point: Vector, center: Vector, size: Vector) -> f32 {
+ let diff = center - point;
+ let q = diff.map(|n| n.abs()) - size / 2.0;
+ return q.map(|n| n.max(0.0)).magnitude() + (q.y.max(q.z).max(q.x)).min(0.0)
+}
+
+pub fn sd_gear(point: Vector, time: f32, center: Vector, radius: f32, thickness: f32, turn_rate: f32) -> f32 {
+ let mut dist: f32;
+
+ let thickness_over_2 = thickness / 2.0;
+ let thickness_over_4 = thickness / 4.0;
+
+ // Ring
+ {
+ let cylinder_dist = (Vector::new(0.0, point.y, point.z) - center).magnitude() - (radius - thickness_over_4);
+ dist = cylinder_dist.abs() - thickness_over_2; // Make cylinder hollow
+ }
+ // Teeth
+ {
+ let sector_angle: f32 = 2.0 * PI / 12.0;
+
+ // Account for rotation with time
+ let angle = sector_angle * time / turn_rate;
+ let rotated_point = Vector::new(
+ point.x,
+ point.y * angle.cos() - point.z * angle.sin(),
+ point.y * angle.sin() + point.z * angle.cos()
+ );
+
+ // Map all space to the first sector
+ let point_angle = (rotated_point.z / rotated_point.y).atan();
+ let angle2 = -sector_angle * (point_angle / sector_angle).round();
+
+ let mapped_point = Vector::new(
+ rotated_point.x,
+ (rotated_point.y * angle2.cos() - rotated_point.z * angle2.sin()).abs(),
+ rotated_point.y * angle2.sin() + rotated_point.z * angle2.cos()
+ );
+
+ let center = Vector { x: 0.0, y: radius + thickness_over_2, z: 0.0 };
+ let size = Vector::new(thickness, thickness * 2.0, thickness);
+ // Make teeth smooth by subtracting some amount
+ dist = dist.min(sd_box(mapped_point, center, size) - thickness_over_4);
+ }
+
+ // Take a slice
+ dist = dist.max(point.x.abs() - thickness_over_2);
+
+ return dist;
+}