commit a715710dd2cbe75103d7f26405a4e3b9fdef4fe2 Author: Codinget Date: Wed Apr 14 19:14:35 2021 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f40c8f1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/target +/.idea +/rmarcher.iml +/a.* diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..02c3b34 --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,455 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "adler32" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" + +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "bitflags" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" + +[[package]] +name = "bstr" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d" +dependencies = [ + "memchr", +] + +[[package]] +name = "bytemuck" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bed57e2090563b83ba8f83366628ce535a7584c9afa4c9fc0612a03925c6df58" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cc" +version = "1.0.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "crc32fast" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd01a6eb3daaafa260f6fc94c3a6c36390abc2080e38e3e34ced87393fb77d80" +dependencies = [ + "cfg-if", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-epoch", + "crossbeam-queue", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2584f639eb95fea8c798496315b297cf81b9b58b6d30ab066a75455333cf4b12" +dependencies = [ + "cfg-if", + "crossbeam-utils", + "lazy_static", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-queue" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f6cb3c7f5b8e51bc3ebb73a2327ad4abdbd119dc13223f14f961d2f38486756" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49" +dependencies = [ + "autocfg", + "cfg-if", + "lazy_static", +] + +[[package]] +name = "deflate" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" +dependencies = [ + "adler32", + "byteorder", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "getrandom" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gif" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a668f699973d0f573d15749b7002a9ac9e1f9c6b220e7b165601334c173d8de" +dependencies = [ + "color_quant", + "weezl", +] + +[[package]] +name = "hermit-abi" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" +dependencies = [ + "libc", +] + +[[package]] +name = "image" +version = "0.23.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24ffcb7e7244a9bf19d35bf2883b9c080c4ced3c07a9895572178cdb8f13f6a1" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "gif", + "jpeg-decoder", + "num-iter", + "num-rational", + "num-traits", + "png", + "scoped_threadpool", + "tiff", +] + +[[package]] +name = "jpeg-decoder" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" +dependencies = [ + "rayon", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41" + +[[package]] +name = "memchr" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" + +[[package]] +name = "memoffset" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83fb6581e8ed1f85fd45c116db8405483899489e38406156c25eb743554361d" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" +dependencies = [ + "adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" +dependencies = [ + "adler", + "autocfg", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "png" +version = "0.16.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" +dependencies = [ + "bitflags", + "crc32fast", + "deflate", + "miniz_oxide 0.3.7", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" + +[[package]] +name = "rand" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", + "rand_hc", +] + +[[package]] +name = "rand_chacha" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34cf66eb183df1c5876e2dcf6b13d57340741e8dc255b48e40a26de954d06ae7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rand_hc" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" +dependencies = [ + "rand_core", +] + +[[package]] +name = "rayon" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + +[[package]] +name = "rlua" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25fa5b2c667bae0b6218361e96d365e414fe4a0fa80f476b9631aa2dea2c6881" +dependencies = [ + "bitflags", + "bstr", + "cc", + "libc", + "num-traits", +] + +[[package]] +name = "rmarcher" +version = "0.1.0" +dependencies = [ + "crossbeam", + "crossbeam-channel", + "image", + "rand", + "rlua", +] + +[[package]] +name = "scoped_threadpool" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "tiff" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437" +dependencies = [ + "jpeg-decoder", + "miniz_oxide 0.4.4", + "weezl", +] + +[[package]] +name = "wasi" +version = "0.10.2+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" + +[[package]] +name = "weezl" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a32b378380f4e9869b22f0b5177c68a5519f03b3454fde0b291455ddbae266c" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..5fae755 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "rmarcher" +version = "0.1.0" +authors = ["codinget"] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand = "0.8.3" +crossbeam-channel = "0.5.0" +crossbeam = "0.8.0" +image = "0.23.14" +rlua = "0.17.0" + +[profile.dev] +opt-level = 3 \ No newline at end of file diff --git a/src/consts.rs b/src/consts.rs new file mode 100644 index 0000000..c9d728b --- /dev/null +++ b/src/consts.rs @@ -0,0 +1,43 @@ +use crate::structs::{Vec3, Y, X, Z}; + +pub const COLOR_CHANNELS: usize = 4; + +pub const EPSILON: f64 = 1. / 1024.; +pub const LIGHT_EPSILON: f64 = 1. / 512.; +pub const MAX_DIST: f64 = 16.; +pub const MAX_STEPS: u32 = 1024; + +pub const DIST_FIX_CORRECTION: f64 = 0.5; +pub const DIST_CORRECTION: f64 = 3.; +pub const DIST_POWER: f64 = 2.; + +pub const ANGLE_FIX_CORRECTION: f64 = 0.; +pub const ANGLE_CORRECTION: f64 = 2.; +pub const ANGLE_POWER: f64 = 2.; + +//pub const IMG_WIDTH: usize = 480; +//pub const IMG_WIDTH: usize = 1280; +pub const IMG_WIDTH: usize = 1080; +//pub const IMG_HEIGHT: usize = 480; +//pub const IMG_HEIGHT: usize = 720; +pub const IMG_HEIGHT: usize = 1080; +pub const IMG_DIM: usize = if IMG_HEIGHT > IMG_WIDTH { IMG_HEIGHT } else { IMG_WIDTH }; +pub const IMG_SIZE: usize = IMG_WIDTH * IMG_HEIGHT; +pub const IMG_BYTE_SIZE: usize = IMG_SIZE * 3; + +//pub const SUPERSAMPLING: usize = 1; +pub const SUPERSAMPLING: usize = 2; +//pub const RAYS_PER_PIXEL: usize = 1; +//pub const RAYS_PER_PIXEL: usize = 50; +pub const RAYS_PER_PIXEL: usize = 500; +//pub const MAX_BOUNCES: u32 = 1; +//pub const MAX_BOUNCES: u32 = 4; +//pub const MAX_BOUNCES: u32 = 8; +pub const MAX_BOUNCES: u32 = 10; + +pub const THREAD_COUNT: usize = 12; +pub const SLICES_PER_THREAD: usize = 4; + +pub const UP: Vec3 = Y; +pub const RIGHT: Vec3 = X; +pub const FORWARD: Vec3 = Z; \ No newline at end of file diff --git a/src/light.rs b/src/light.rs new file mode 100644 index 0000000..19e44f7 --- /dev/null +++ b/src/light.rs @@ -0,0 +1,16 @@ +use crate::structs::{ColorVec, Vec3}; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Light { + pos: Vec3, + color: ColorVec, +} + +impl Light { + pub fn new(pos: Vec3, color: ColorVec) -> Light { + Light { pos, color } + } + + pub fn pos(&self) -> Vec3 { self.pos } + pub fn color(&self) -> ColorVec { self.color } +} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..9e2ab4f --- /dev/null +++ b/src/main.rs @@ -0,0 +1,84 @@ +mod material; +mod light; +mod object; +mod structs; +mod consts; + +use object::*; +use crate::consts::*; +use crate::structs::*; +use crate::material::*; +use image::{ColorType, ImageFormat}; +use image::codecs::png::FilterType::Sub; + +fn default_cam() -> Cam { + Cam::new_pointing(Y*3. - X*5., O, 0.5) +} + +fn default_scene() -> Scene { + let s0 = WithMaterial::new( + Sphere::new_xyz(0., 0., 0., 1.), + RED + ); + let s1 = WithMaterial::new( + Sphere::new_xyz(0., 1., 1., 0.5), + MIRROR + ); + let spheres = Union::new(s0, s1); + let floor = WithMaterial::new( + Plane::new_xyz(0., 1., 0., 2.), + WHITE + ); + let backwall = WithMaterial::new( + Plane::new_xyz(-1., 0., 0., 2.), + BLUE + ); + let sidewalls = WithMaterial::new( + Union::new( + Plane::new_xyz(0., 0., -1., 2.5), + Plane::new_xyz(0., 0., 1., 2.5) + ), + GREEN + ); + let walls = Union::new( + Union::new(floor, backwall), + sidewalls + ); + let scene = Union::new(spheres, walls); + let light = WithMaterial::new( + Plane::new_xyz(0., -1., 0., 8.), + LIGHTSOURCE + ); + let scene = Union::new(scene, light); + Scene::new(scene) +} + +fn main() { + // get scene and camera + let scene = default_scene(); + let cam = default_cam(); + + // get stats on the scene we're about to render + let total_rpp = (RAYS_PER_PIXEL * SUPERSAMPLING * SUPERSAMPLING) as u64; + let lights = scene.get_lights().len() as u32; + let nodes = scene.node_count(); + let min_rays = IMG_SIZE as u64 * RAYS_PER_PIXEL as u64 * SUPERSAMPLING as u64; + let max_rays = min_rays * MAX_BOUNCES as u64 * (1+lights) as u64; + println!("Image size: {}x{} ({} pixels)", IMG_WIDTH, IMG_HEIGHT, IMG_SIZE); + println!("Rpp: {}, {}x supersampling ({} total rays per image pixel)", RAYS_PER_PIXEL, SUPERSAMPLING, total_rpp); + println!("Max bounces: {}, dist: {}, steps: {}", MAX_BOUNCES, MAX_DIST, MAX_STEPS); + println!("Lights: {}, Nodes: {}", lights, nodes); + println!("Threads: {}, {} slices per thread", THREAD_COUNT, SLICES_PER_THREAD); + println!("Total rays: min {} max {}", min_rays, max_rays); + + // generate image and save it + let data = cam.render(&scene); + image::save_buffer_with_format( + "a.png", + &data, + IMG_WIDTH as u32, + IMG_HEIGHT as u32, + ColorType::Rgb8, + ImageFormat::Png + ).unwrap(); +} diff --git a/src/material.rs b/src/material.rs new file mode 100644 index 0000000..31dde01 --- /dev/null +++ b/src/material.rs @@ -0,0 +1,88 @@ +use crate::structs::{ColorVec, ColorMat}; +use crate::consts::COLOR_CHANNELS; +use crate::material::SurfaceType::{Diffuse, Reflective, Stop}; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum SurfaceType { + Diffuse, + Reflective, + Stop +} + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Material { + emission: ColorVec, + reflection: ColorMat, + surface: SurfaceType +} + +impl Material { + pub const fn new(emission: ColorVec, reflection: ColorMat, surface: SurfaceType) -> Material { + Material { emission, reflection, surface } + } + + pub const fn new_from_diagonal(emission: ColorVec, reflection: ColorVec, surface: SurfaceType) -> Material { + Material { emission, reflection: ColorMat::new_from_diagonal(reflection), surface } + } + + pub const fn emission(&self) -> ColorVec { self.emission } + pub const fn reflection(&self) -> ColorMat { self.reflection } + pub const fn surface(&self) -> SurfaceType { self.surface } +} + +const COLOR_ZERO: ColorVec = ColorVec::new_zero(); +const COLOR_ONE: ColorVec = ColorVec::new([1.; COLOR_CHANNELS]); + +pub const WHITE: Material = Material::new_from_diagonal( + COLOR_ZERO, + COLOR_ONE, + Diffuse +); +pub const RED: Material = Material::new_from_diagonal( + COLOR_ZERO, + ColorVec::new([0.75, 0.25, 0.25, 0.]), + Diffuse +); +pub const GREEN: Material = Material::new_from_diagonal( + COLOR_ZERO, + ColorVec::new([0.25, 0.75, 0.25, 0.]), + Diffuse +); +pub const BLUE: Material = Material::new_from_diagonal( + COLOR_ZERO, + ColorVec::new([0.25, 0.25, 0.75, 0.]), + Diffuse +); +pub const LIGHTSOURCE: Material = Material::new_from_diagonal( + ColorVec::new([1., 1., 1., 0.]), + COLOR_ONE, + Diffuse +); + +pub const MIRROR: Material = Material::new_from_diagonal( + COLOR_ZERO, + ColorVec::new([0.9, 0.9, 0.9, 0.9]), + Reflective +); + +pub const STRONG_LIGHTSOURCE: Material = Material::new_from_diagonal( + ColorVec::new([5., 5., 5., 0.]), + COLOR_ZERO, + Stop +); + +pub const UV_LIGHTSOURCE: Material = Material::new_from_diagonal( + ColorVec::new_one(3), + ColorVec::new([0.25, 0.25, 0.25, 1.]), + Diffuse +); +pub const FLUORESCENT: Material = Material::new( + ColorVec::new([0.25; COLOR_CHANNELS]), + ColorMat::new([ + [1., 0., 0., 0.75], + [0., 1., 0., 0.25], + [0., 0., 1., 0.], + [0., 0., 0., 0.] + ]), + Diffuse +); \ No newline at end of file diff --git a/src/object/cuboid.rs b/src/object/cuboid.rs new file mode 100644 index 0000000..3a1524c --- /dev/null +++ b/src/object/cuboid.rs @@ -0,0 +1,65 @@ +use crate::structs::Vec3; +use crate::object::Obj; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Cuboid { + center: Vec3, + radius: Vec3 +} + +impl Cuboid { + pub fn new(center: Vec3, radius: Vec3) -> Cuboid { + Cuboid { center, radius } + } + pub fn new_xyz(cx: f64, cy: f64, cz: f64, rx: f64, ry: f64, rz: f64) -> Cuboid { + let center = Vec3::new(cx, cy, cz); + let radius = Vec3::new(rx, ry, rz); + Cuboid { center, radius } + } +} + +impl Obj for Cuboid { + fn distance_to(&self, point: Vec3) -> f64 { + let dx = point.x() - self.center.x(); + let dy = point.y() - self.center.y(); + let dz = point.z() - self.center.z(); + + if dx.abs() Vec3 { + let dx = (point.x()-self.center.y())/self.radius.x(); + let dy = (point.y()-self.center.y())/self.radius.y(); + let dz = (point.z()-self.center.z())/self.radius.z(); + let (adx, ady, adz) = (dx.abs(), dy.abs(), dz.abs()); + + Vec3::new( + if adx>=ady && adx>adz { if dx>0. { 1. } else { -1. }} else { 0. }, + if ady>=adz && ady>adx { if dy>0. { 1. } else { -1. }} else { 0. }, + if adz>=adx && adz>ady { if dz>0. { 1. } else { -1. }} else { 0. }, + ) + } +} \ No newline at end of file diff --git a/src/object/cylinder.rs b/src/object/cylinder.rs new file mode 100644 index 0000000..d179421 --- /dev/null +++ b/src/object/cylinder.rs @@ -0,0 +1,23 @@ +use crate::structs::Vec3; +use crate::object::Obj; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Cylinder { + center: Vec3, + radius: f64 +} + +impl Cylinder { + pub const fn new(center: Vec3, radius: f64) -> Cylinder { + Cylinder { center, radius } + } +} + +impl Obj for Cylinder { + fn distance_to(&self, point: Vec3) -> f64 { + Vec3::new(self.center.x(), 0., self.center.z()).distance_to(point) - self.radius + } + fn normal_at(&self, point: Vec3) -> Vec3 { + point - Vec3::new(self.center.x(), 0.,self.center.z()) + } +} diff --git a/src/object/intersection.rs b/src/object/intersection.rs new file mode 100644 index 0000000..9d32df8 --- /dev/null +++ b/src/object/intersection.rs @@ -0,0 +1,45 @@ +use crate::object::Obj; +use crate::light::Light; +use crate::structs::Vec3; +use std::vec; +use crate::material::Material; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Intersection { + a: A, + b: B +} + +impl Intersection { + pub fn new(a: A, b: B) -> Intersection { + Intersection { a, b } + } +} + +impl Obj for Intersection { + fn distance_to(&self, point: Vec3) -> f64 { + f64::max(self.a.distance_to(point), self.b.distance_to(point)) + } + fn normal_at(&self, point: Vec3) -> Vec3 { + if self.a.distance_to(point) > self.b.distance_to(point) { + self.a.normal_at(point) + } else { + self.b.normal_at(point) + } + } + fn material_at(&self, point: Vec3) -> Material { + if self.a.distance_to(point) > self.b.distance_to(point) { + self.a.material_at(point) + } else { + self.b.material_at(point) + } + } + fn get_lights(&self) -> vec::Vec { + let mut l = self.a.get_lights(); + l.extend(self.b.get_lights()); + l + } + fn node_count(&self) -> u32 { + self.a.node_count() + self.b.node_count() + 1 + } +} diff --git a/src/object/mod.rs b/src/object/mod.rs new file mode 100644 index 0000000..4748c59 --- /dev/null +++ b/src/object/mod.rs @@ -0,0 +1,71 @@ +use crate::consts::{EPSILON, MAX_DIST}; +use crate::structs::{Vec3, O, X, Y, Z}; +use crate::material::Material; +use crate::material; +use crate::light::Light; +use std::vec::Vec; + +mod sphere; +mod plane; +mod union; +mod intersection; +mod cuboid; +mod cylinder; +mod torus; +mod waves; +mod with_material; +mod with_lights; +mod transform; +mod scene; + +pub trait Obj: Send + Sync { + fn distance_to(&self, _point: Vec3) -> f64 { + MAX_DIST + } + fn normal_at(&self, point: Vec3) -> Vec3 { + let v = self.distance_to(point); + let x = self.distance_to(point + X*EPSILON) - v; + let y = self.distance_to(point + Y*EPSILON) - v; + let z = self.distance_to(point + Z*EPSILON) - v; + + Vec3::new(x, y, z).unit() + } + fn material_at(&self, _point: Vec3) -> Material { + material::WHITE + } + fn get_lights(&self) -> Vec { + Vec::new() + } + fn node_count(&self) -> u32 { 1 } +} + +impl Obj for &T { + fn distance_to(&self, point: Vec3) -> f64 { (*self).distance_to(point) } + fn normal_at(&self, point: Vec3) -> Vec3 { (*self).normal_at(point) } + fn material_at(&self, point: Vec3) -> Material { (*self).material_at(point) } + fn get_lights(&self) -> Vec { (*self).get_lights() } + fn node_count(&self) -> u32 { (*self).node_count() } +} + +pub trait ObjClone: Obj { + fn clone_obj(&self) -> Box; +} + +impl ObjClone for T { + fn clone_obj(&self) -> Box { + Box::new(self.clone()) + } +} + +pub use sphere::Sphere; +pub use plane::Plane; +pub use union::Union; +pub use intersection::Intersection; +pub use cuboid::Cuboid; +pub use cylinder::Cylinder; +pub use torus::Torus; +pub use waves::Waves; +pub use with_material::{WithMaterial, WithDynamicMaterial}; +pub use with_lights::{WithLights, WithLight}; +pub use transform::AffineTransform; +pub use scene::Scene; \ No newline at end of file diff --git a/src/object/plane.rs b/src/object/plane.rs new file mode 100644 index 0000000..d9f7ab2 --- /dev/null +++ b/src/object/plane.rs @@ -0,0 +1,31 @@ +use crate::structs::Vec3; +use crate::object::Obj; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Plane { + normal: Vec3, + offset: f64 +} + +impl Plane { + pub fn new(normal: Vec3, offset: f64) -> Plane { + let l = normal.magnitude(); + Plane { normal: normal/l, offset: offset/l } + } + pub fn new_xyz(x: f64, y: f64, z: f64, offset: f64) -> Plane { + let normal = Vec3::new(x, y, z); + Plane::new(normal, offset) + } + pub const unsafe fn new_raw(normal: Vec3, offset: f64) -> Plane { + Plane { normal, offset } + } +} + +impl Obj for Plane { + fn distance_to(&self, point: Vec3) -> f64 { + point*self.normal + self.offset + } + fn normal_at(&self, _point: Vec3) -> Vec3 { + self.normal + } +} \ No newline at end of file diff --git a/src/object/scene.rs b/src/object/scene.rs new file mode 100644 index 0000000..4ce8241 --- /dev/null +++ b/src/object/scene.rs @@ -0,0 +1,31 @@ +use crate::object::{Obj, ObjClone}; +use crate::structs::Vec3; +use crate::material::Material; +use crate::light::Light; + +pub struct Scene { + scene: Box +} + +impl Scene { + pub fn new(scene: T) -> Scene { + Scene { scene: Box::new(scene) } + } + pub fn new_from_box(scene: Box) -> Scene { + Scene { scene } + } +} + +impl Obj for Scene { + fn distance_to(&self, point: Vec3) -> f64 { self.scene.distance_to(point) } + fn normal_at(&self, point: Vec3) -> Vec3 { self.scene.normal_at(point) } + fn material_at(&self, point: Vec3) -> Material { self.scene.material_at(point) } + fn get_lights(&self) -> Vec { self.scene.get_lights() } + fn node_count(&self) -> u32 { self.scene.node_count() + 1 } +} + +impl Clone for Scene { + fn clone(&self) -> Scene { + Scene { scene: self.scene.clone_obj() } + } +} \ No newline at end of file diff --git a/src/object/sphere.rs b/src/object/sphere.rs new file mode 100644 index 0000000..6ed349c --- /dev/null +++ b/src/object/sphere.rs @@ -0,0 +1,27 @@ +use crate::structs::Vec3; +use crate::object::Obj; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Sphere { + pos: Vec3, + radius: f64 +} + +impl Sphere { + pub const fn new(pos: Vec3, radius: f64) -> Sphere { + Sphere { pos, radius } + } + pub const fn new_xyz(x: f64, y: f64, z: f64, radius: f64) -> Sphere { + let pos = Vec3::new(x, y, z); + Sphere { pos, radius } + } +} + +impl Obj for Sphere { + fn distance_to(&self, point: Vec3) -> f64 { + self.pos.distance_to(point) - self.radius + } + fn normal_at(&self, point: Vec3) -> Vec3 { + (point - self.pos).unit() + } +} \ No newline at end of file diff --git a/src/object/torus.rs b/src/object/torus.rs new file mode 100644 index 0000000..7e80648 --- /dev/null +++ b/src/object/torus.rs @@ -0,0 +1,34 @@ +use crate::structs::Vec3; +use crate::object::Obj; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Torus { + center: Vec3, + radius: f64, + thickness: f64 +} + +impl Torus { + pub const fn new(center: Vec3, radius: f64, thickness: f64) -> Torus { + Torus { center, radius, thickness } + } + pub const fn new_xyz(x: f64, y: f64, z: f64, radius: f64, thickness: f64) -> Torus { + Torus::new(Vec3::new(x, y, z), radius, thickness) + } +} + +impl Obj for Torus { + fn distance_to(&self, point: Vec3) -> f64 { + let (dx, dy) = (point.x()-self.center.x(), point.y()-self.center.y()); + let dh = f64::abs(f64::sqrt(dx*dx + dy*dy) - self.radius); + let dz = point.z() - self.center.z(); + f64::sqrt(dh*dh + dz*dz) - self.thickness + } + + fn normal_at(&self, point: Vec3) -> Vec3 { + let centered = point - self.center; + let centered_no_z= Vec3::new(centered.x(), centered.y(), 0.); + let closest = centered_no_z.unit() * self.radius; + (centered - closest).unit() + } +} \ No newline at end of file diff --git a/src/object/transform.rs b/src/object/transform.rs new file mode 100644 index 0000000..4523d73 --- /dev/null +++ b/src/object/transform.rs @@ -0,0 +1,83 @@ +use crate::object::Obj; +use crate::structs::{Mat3, Vec3, O, I3}; +use crate::material::Material; +use crate::light::Light; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct AffineTransform { + obj: T, + transform: Mat3, + transform_inv: Mat3, + translate: Vec3 +} + +impl AffineTransform { + pub fn new(obj: T, transform: Mat3, translate: Vec3) -> AffineTransform { + AffineTransform { obj, transform, transform_inv: transform.invert(), translate } + } + pub fn new_linear(obj: T, transform: Mat3) -> AffineTransform { + AffineTransform { obj, transform, transform_inv: transform.invert(), translate: O } + } + pub fn new_translate(obj: T, translate: Vec3) -> AffineTransform { + AffineTransform { obj, transform: I3, transform_inv: I3, translate } + } + + fn apply_rev(&self, point: Vec3) -> Vec3 { + self.transform_inv*point - self.translate + } + fn apply_fwd(&self, point: Vec3) -> Vec3 { + self.transform*point + self.translate + } +} + +pub const SWAP_XY: Mat3 = Mat3::new( + 0., 1., 0., + 1., 0., 0., + 0., 0., 1., +); +pub const SWAP_XZ: Mat3 = Mat3::new( + 0., 0., 1., + 0., 1., 0., + 1., 0., 0., +); +pub const SWAP_YZ: Mat3 = Mat3::new( + 1., 0., 0., + 0., 0., 1., + 0., 1., 0., +); + +pub const fn scale_xyz(x: f64, y: f64, z: f64) -> Mat3 { + Mat3::new( + x , 0., 0., + 0., y , 0., + 0., 0., z , + ) +} +pub const fn scale(k: f64) -> Mat3 { scale_xyz(k, k, k) } +pub const fn scale_x(k: f64) -> Mat3 { scale_xyz(k, 1., 1.) } +pub const fn scale_y(k: f64) -> Mat3 { scale_xyz(1., k, 1.) } +pub const fn scale_z(k: f64) -> Mat3 { scale_xyz(1., 1., k) } + +impl Obj for AffineTransform { + fn distance_to(&self, point: Vec3) -> f64 { + self.obj.distance_to(self.apply_rev(point)) + } + fn normal_at(&self, point: Vec3) -> Vec3 { + self.apply_fwd(self.obj.normal_at(self.apply_rev(point))) + } + fn material_at(&self, point: Vec3) -> Material { + self.obj.material_at(self.apply_rev(point)) + } + fn get_lights(&self) -> Vec { + self.obj + .get_lights() + .into_iter() + .map(|light| { + Light::new(self.apply_fwd(light.pos()), light.color()) + }) + .collect() + } + fn node_count(&self) -> u32 { + self.obj.node_count() + 1 + } +} \ No newline at end of file diff --git a/src/object/union.rs b/src/object/union.rs new file mode 100644 index 0000000..1507407 --- /dev/null +++ b/src/object/union.rs @@ -0,0 +1,45 @@ +use crate::object::Obj; +use crate::light::Light; +use crate::structs::Vec3; +use std::vec; +use crate::material::Material; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Union { + a: A, + b: B +} + +impl Union { + pub fn new(a: A, b: B) -> Union { + Union { a, b } + } +} + +impl Obj for Union { + fn distance_to(&self, point: Vec3) -> f64 { + f64::min(self.a.distance_to(point), self.b.distance_to(point)) + } + fn normal_at(&self, point: Vec3) -> Vec3 { + if self.a.distance_to(point) < self.b.distance_to(point) { + self.a.normal_at(point) + } else { + self.b.normal_at(point) + } + } + fn material_at(&self, point: Vec3) -> Material { + if self.a.distance_to(point) < self.b.distance_to(point) { + self.a.material_at(point) + } else { + self.b.material_at(point) + } + } + fn get_lights(&self) -> vec::Vec { + let mut l = self.a.get_lights(); + l.extend(self.b.get_lights()); + l + } + fn node_count(&self) -> u32 { + self.a.node_count() + self.b.node_count() + 1 + } +} \ No newline at end of file diff --git a/src/object/waves.rs b/src/object/waves.rs new file mode 100644 index 0000000..5b204ce --- /dev/null +++ b/src/object/waves.rs @@ -0,0 +1,22 @@ +use crate::object::Obj; +use crate::structs::Vec3; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Waves {} + +impl Waves { + pub const fn new() -> Waves { + Waves {} + } +} + +impl Obj for Waves { + fn distance_to(&self, point: Vec3) -> f64 { + let dist = point.x().sin() + point.y().sin() + point.z(); + dist / f64::sqrt(3.) + } + + fn normal_at(&self, point: Vec3) -> Vec3 { + Vec3::new(-point.x().cos(), -point.y().cos(), 1.).unit() + } +} \ No newline at end of file diff --git a/src/object/with_lights.rs b/src/object/with_lights.rs new file mode 100644 index 0000000..f22ac71 --- /dev/null +++ b/src/object/with_lights.rs @@ -0,0 +1,44 @@ +use crate::light::Light; +use crate::object::Obj; +use crate::structs::Vec3; +use crate::material::Material; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct WithLights { + obj: T, + lights: [Light; N] +} + +impl WithLights { + pub fn new(obj: T, lights: [Light; N]) -> WithLights { + WithLights { obj, lights } + } +} + +impl Obj for WithLights { + fn distance_to(&self, point: Vec3) -> f64 { + self.obj.distance_to(point) + } + fn normal_at(&self, point: Vec3) -> Vec3 { + self.obj.normal_at(point) + } + fn material_at(&self, point: Vec3) -> Material { + self.obj.material_at(point) + } + fn get_lights(&self) -> Vec { + let mut l = self.obj.get_lights(); + l.extend(&self.lights); + l + } + fn node_count(&self) -> u32 { + self.obj.node_count() + 1 + } +} + +pub type WithLight = WithLights; + +impl WithLight { + pub fn new_one(obj: T, light: Light) -> WithLight { + WithLight { obj, lights: [light; 1] } + } +} \ No newline at end of file diff --git a/src/object/with_material.rs b/src/object/with_material.rs new file mode 100644 index 0000000..80af1b8 --- /dev/null +++ b/src/object/with_material.rs @@ -0,0 +1,64 @@ +use crate::object::Obj; +use crate::material::Material; +use crate::structs::Vec3; +use crate::light::Light; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct WithMaterial { + obj: T, + material: Material +} + +impl WithMaterial { + pub fn new(obj: T, material: Material) -> WithMaterial { + WithMaterial { obj, material } + } +} + +impl Obj for WithMaterial { + fn distance_to(&self, point: Vec3) -> f64 { + self.obj.distance_to(point) + } + fn normal_at(&self, point: Vec3) -> Vec3 { + self.obj.normal_at(point) + } + fn material_at(&self, point: Vec3) -> Material { + self.material + } + fn get_lights(&self) -> Vec { + self.obj.get_lights() + } + fn node_count(&self) -> u32 { + self.obj.node_count() + 1 + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct WithDynamicMaterial Material> { + obj: T, + fun: F +} + +impl Material> WithDynamicMaterial { + pub fn new(obj: T, fun: F) -> WithDynamicMaterial { + WithDynamicMaterial { obj, fun } + } +} + +impl Material> Obj for WithDynamicMaterial { + fn distance_to(&self, point: Vec3) -> f64 { + self.obj.distance_to(point) + } + fn normal_at(&self, point: Vec3) -> Vec3 { + self.obj.normal_at(point) + } + fn material_at(&self, point: Vec3) -> Material { + (self.fun)(point) + } + fn get_lights(&self) -> Vec { + self.obj.get_lights() + } + fn node_count(&self) -> u32 { + self.obj.node_count() + 1 + } +} \ No newline at end of file diff --git a/src/structs/cam.rs b/src/structs/cam.rs new file mode 100644 index 0000000..f205c74 --- /dev/null +++ b/src/structs/cam.rs @@ -0,0 +1,323 @@ +use crate::structs::{Vec3, ColorVec, COLOR_ZERO, Ray, Y, X}; +use crate::object::Obj; +use crate::material::{Material, SurfaceType}; +use crate::consts::*; +use crate::light::Light; +use std::f64::consts::PI; +use rand::prelude::*; +use crossbeam_channel::unbounded; +use std::ops::{Mul, Add, Sub}; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Cam { + pos: Vec3, + dir: Vec3, + up: Vec3, + right: Vec3 +} + +impl Cam { + pub fn new(pos: Vec3, dir: Vec3, fov: f64) -> Cam { + let right = (dir ^ UP).unit() * fov; + let up = (right ^ dir).unit() * fov; + let dir = dir.unit(); + Cam { pos, dir, up, right } + } + + pub fn new_pointing(pos: Vec3, pointing: Vec3, fov: f64) -> Cam { + Self::new(pos, pointing - pos, fov) + } +} + +#[derive(Debug, Copy, Clone, PartialEq)] +struct RayData { + pub pos: Vec3, + pub dir: Vec3, + pub normal: Vec3, + pub material: Material, + pub dist: f64, + pub steps: u32 +} + +#[derive(Debug, Copy, Clone, PartialEq)] +struct LightRayData<'a> { + pub light: &'a Light, + pub dir: Vec3, + pub dist: f64 +} + +#[derive(Debug, Copy, Clone, PartialEq)] +struct Slice { + pub x: usize, + pub y: usize, + pub w: usize, + pub h: usize +} + +type CastData = [Option; MAX_BOUNCES as usize]; +const NONE_CAST_DATA: CastData = [None; MAX_BOUNCES as usize]; +pub type Image = [u8; IMG_BYTE_SIZE]; + +fn l2s(l: f64) -> u8 { + (f64::powf(f64::clamp(l, 0., 1.), 1./2.2) * 255. + 0.5) as u8 +} + +fn randab(a: f64, b: f64) -> f64 { + random::() * (b-a) + a +} + +fn shoot_ray(scene: &T, pos: Vec3, dir: Vec3) -> Option { + let dir = dir.unit(); + let mut steps = 0; + let mut dist = 0.; + let ray = Ray::new(pos, dir); + while steps < MAX_STEPS && dist < MAX_DIST { + let pos = ray.point(dist); + let d = scene.distance_to(pos); + if d < EPSILON { + return Some(RayData { + pos, + dir, + normal: scene.normal_at(pos), + material: scene.material_at(pos), + dist, + steps + }) + } + dist += d; + steps += 1; + } + None +} + +fn shoot_ray_at(scene: &T, pos: Vec3, dest: Vec3) -> bool { + let dir = (dest - pos).unit(); + let mut steps = 0; + let mut dist = EPSILON; //XXX 0. + let ray = Ray::new(pos, dir); + while steps < MAX_STEPS && dist < MAX_DIST { + let point = ray.point(dist); + let sd = scene.distance_to(point); + let dd = dest.distance_to(point); + if sd < 0. { + return false; + } + if dd < sd { + return true; + } + dist += if sd < EPSILON { EPSILON } else { sd }; + steps += 1; + } + false +} + +impl Cam { + pub fn render(&self, scene: &T) -> Image { + if THREAD_COUNT > 1 { + self.render_multithreaded(scene) + } else { + self.render_singlethreaded(scene) + } + } + + pub fn render_singlethreaded(&self, scene: &T) -> Image { + let lights = scene.get_lights(); + let mut pixels = [0; IMG_BYTE_SIZE]; + for y in 0..IMG_HEIGHT { + for x in 0..IMG_WIDTH { + let field_pos = (x + y*IMG_WIDTH) * 3; + self.render_single(scene, &lights, x, y, &mut pixels[field_pos..(field_pos+3)]); + } + } + pixels + } + + pub fn render_multithreaded(&self, scene: &T) -> Image { + let mut pixels = [0; IMG_BYTE_SIZE]; + + crossbeam::scope(|scope| { + // initialize everything for render + let lights = scene.get_lights(); + let slice_width = IMG_WIDTH / THREAD_COUNT; + let slice_height = IMG_HEIGHT / SLICES_PER_THREAD; + let slice_byte_size = slice_width * slice_height * 3; + + let (slice_tx, slice_rx) = unbounded::(); + let (data_tx, data_rx) = unbounded(); + + // spawn a bunch of threads + for _i in 0..THREAD_COUNT { + let cam = self.clone(); + let scene = scene.clone(); + let lights = lights.clone(); + let slice_rx = slice_rx.clone(); + let data_tx = data_tx.clone(); + + scope.spawn(move |_| { + for slice in slice_rx.iter() { + let mut pixels = Vec::with_capacity(slice_byte_size); + pixels.resize(slice_byte_size, 0 as u8); + let data = pixels.as_mut_slice(); + + for x in 0..slice.w { + for y in 0..slice.h { + let i = (x + y*slice_width) * 3; + cam.render_single(&scene, &lights, slice.x+x, slice.y+y, &mut data[i..(i+3)]); + } + } + + data_tx.send((slice, pixels)).unwrap(); + } + }); + } + + // don't forget to close the ends of the channel we don't use + drop(slice_rx); + drop(data_tx); + + // send the slice data + let mut y = 0; + while y < IMG_HEIGHT { + let mut x = 0; + while x < IMG_WIDTH { + let w = usize::min(slice_width, IMG_WIDTH - x); + let h = usize::min(slice_height, IMG_HEIGHT - y); + slice_tx.send(Slice { x, y, w, h }).unwrap(); + x += slice_width; + } + y += slice_height; + } + drop(slice_tx); + + // merge stuff as we get it + for (slice, data) in data_rx { + let data = data.as_slice(); + for sy in 0..slice.h { + let py = sy + slice.y; + for sx in 0..slice.w { + let px = sx + slice.x; + + let pi = (px + py * IMG_WIDTH) * 3; + let si = (sx + sy * slice_width) * 3; + pixels[pi + 0] = data[si + 0]; + pixels[pi + 1] = data[si + 1]; + pixels[pi + 2] = data[si + 2]; + } + } + } + }).unwrap(); + + pixels + } + + fn render_single(&self, scene: &T, lights: &Vec, x: usize, y: usize, field: &mut [u8]) -> () { + let mut color = COLOR_ZERO; + for xs in 0..SUPERSAMPLING { + for ys in 0..SUPERSAMPLING { + for _i in 0..RAYS_PER_PIXEL { + let cast = self.cast_single(scene, x * SUPERSAMPLING + xs, y * SUPERSAMPLING + ys); + color = color + self.color_single(scene, &lights, cast); + } + } + } + color = color / (SUPERSAMPLING * SUPERSAMPLING * RAYS_PER_PIXEL) as f64; + + field[0] = l2s(color.v(0)); + field[1] = l2s(color.v(1)); + field[2] = l2s(color.v(2)); + } + + fn color_single<'a, T: Obj>(&self, scene: &'a T, lights: &'a Vec, cast: CastData) -> ColorVec { + let mut color = COLOR_ZERO; + let mut dist: f64 = 1.; + + for i in (0..MAX_BOUNCES).rev() { + if let Some(ray) = cast[i as usize] { + match ray.material.surface() { + SurfaceType::Diffuse => { + color = color / dist.powf(DIST_POWER); + + color = ray.material.reflection() * color + ray.material.emission(); + for lightray in Self::get_lights(scene, lights, ray.pos + (scene.normal_at(ray.pos)- ray.dir) * LIGHT_EPSILON) { + let angle = f64::max(0., ray.dir.angle_to(lightray.dir).abs() / PI / ANGLE_CORRECTION + 1. - ANGLE_FIX_CORRECTION); + let dist = f64::max(1., lightray.dist / DIST_CORRECTION - DIST_FIX_CORRECTION); + color = color + ray.material.reflection() * lightray.light.color() / angle.powf(ANGLE_POWER) / dist.powf(DIST_POWER); + } + }, + SurfaceType::Reflective => { + color = ray.material.reflection() * color + ray.material.emission(); + }, + SurfaceType::Stop => { + color = ray.material.emission(); + } + } + dist = f64::max(1., ray.dist / DIST_CORRECTION - DIST_FIX_CORRECTION); + } + } + + color / dist.powf(DIST_POWER) + } + + fn cast_single(&self, scene: &T, x: usize, y: usize) -> CastData { + let mut cast = NONE_CAST_DATA; + let mut pos = self.pos; + let mut dir = self.dir_for(x, y); + let mut i = 0; + while i < MAX_BOUNCES { + let hit = shoot_ray(scene, pos, dir); + cast[i as usize] = hit; + match hit { + Some(hit) => { + i += 1; + pos = hit.pos - dir * EPSILON; + match hit.material.surface() { + SurfaceType::Stop => break, + SurfaceType::Diffuse => { + let r1 = randab(0., 2.*PI); + let r2 = randab(0., 1.); + let w = hit.normal; + let u = ((if w.x().abs() > 0.5 { Y } else { X }) ^ w).unit(); + let v = w ^ u; + let x = r1.cos()*r2.sqrt(); + let y = r1.sin()*r2.sqrt(); + let z = (1.-r2).sqrt(); + if w.x().abs() < 0.5 { + dir = (u*x + v*y + w*z).unit(); + } else { + dir = (u*x + v*y + w*z).unit(); + } + }, + SurfaceType::Reflective => { + dir = dir - hit.normal * (hit.normal * dir) * 2.; + } + } + }, + None => break, + } + } + cast + } + + fn get_lights<'a, T: Obj>(scene: &'a T, lights: &'a Vec, pos: Vec3) -> Vec> { + lights + .into_iter() + .filter_map(|light| { + if shoot_ray_at(scene, pos, light.pos()) { + Some(LightRayData { + light, + dir: (pos - light.pos()), + dist: pos.distance_to(light.pos()) + }) + } else { + None + } + }) + .collect() + } + + fn dir_for(&self, x: usize, y: usize) -> Vec3 { + let x = (((x + (IMG_DIM - IMG_WIDTH) * SUPERSAMPLING / 2) as f64) / ((IMG_DIM * SUPERSAMPLING) as f64) - 0.5) * 2.; + let y = (0.5 - ((y + (IMG_DIM - IMG_HEIGHT) * SUPERSAMPLING / 2) as f64) / ((IMG_DIM * SUPERSAMPLING) as f64)) * 2.; + (self.dir + self.right * x + self.up * y).unit() + } +} \ No newline at end of file diff --git a/src/structs/mat3.rs b/src/structs/mat3.rs new file mode 100644 index 0000000..aa3af00 --- /dev/null +++ b/src/structs/mat3.rs @@ -0,0 +1,217 @@ +use crate::structs::vec3::Vec3; +use std::ops::{Add, Sub, Neg, Mul, Div}; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Mat3 { + a: f64, b: f64, c: f64, + d: f64, e: f64, f: f64, + g: f64, h: f64, i: f64, +} + +pub const I3: Mat3 = Mat3 { + a: 1., b: 0., c: 0., + d: 0., e: 1., f: 0., + g: 0., h: 0., i: 1., +}; + +pub const O3: Mat3 = Mat3 { + a: 0., b: 0., c: 0., + d: 0., e: 0., f: 0., + g: 0., h: 0., i: 0., +}; + +pub const COFACTORS: Mat3 = Mat3 { + a: 1., b: -1., c: 1., + d: -1., e: 1., f: -1., + g: 1., h: -1., i: 1., +}; + +impl Mat3 { + pub const fn new(a: f64, b: f64, c: f64, d: f64, e: f64, f: f64, g: f64, h: f64, i: f64) -> Mat3 { + Mat3 { + a, b, c, + d, e, f, + g, h, i, + } + } + + pub const fn new_cols(a: Vec3, b: Vec3, c: Vec3) -> Mat3 { + Mat3 { + a: a.x(), b: b.x(), c: c.x(), + d: a.y(), e: b.y(), f: c.y(), + g: a.z(), h: b.z(), i: c.z(), + } + } + + pub const fn new_rows(a: Vec3, d: Vec3, g: Vec3) -> Mat3 { + Mat3 { + a: a.x(), b: a.y(), c: a.z(), + d: d.x(), e: d.y(), f: d.z(), + g: g.x(), h: g.y(), i: g.z(), + } + } + + pub const fn a(&self) -> f64 { self.a } + pub const fn b(&self) -> f64 { self.b } + pub const fn c(&self) -> f64 { self.c } + pub const fn d(&self) -> f64 { self.d } + pub const fn e(&self) -> f64 { self.e } + pub const fn f(&self) -> f64 { self.f } + pub const fn g(&self) -> f64 { self.g } + pub const fn h(&self) -> f64 { self.h } + pub const fn i(&self) -> f64 { self.i } + + pub fn det(&self) -> f64 { + let (a, b, c) = (self.a(), self.b(), self.c()); + let (d, e, f) = (self.d(), self.e(), self.f()); + let (g, h, i) = (self.g(), self.h(), self.i()); + + a*(e*i - f*h) - b*(d*i - f*g) + c*(d*h - e*g) + } + + pub const fn trans(&self) -> Mat3 { + Mat3 { + a: self.a, b: self.d, c: self.g, + d: self.b, e: self.e, f: self.h, + g: self.c, h: self.f, i: self.i, + } + } + + pub fn minor(&self) -> Mat3 { + let (a, b, c) = (self.a(), self.b(), self.c()); + let (d, e, f) = (self.d(), self.e(), self.f()); + let (g, h, i) = (self.g(), self.h(), self.i()); + + Mat3 { + a: e*i - f*h, b: d*i - f*g, c: d*h - e*g, + d: b*i - c*h, e: a*i - c*g, f: a*h - b*g, + g: b*f - c*e, h: a*f - c*d, i: a*e - b*d, + } + } + + pub fn invert(&self) -> Mat3 { + (self.minor().member_mul(COFACTORS)).trans() / self.det() + } + + pub fn member_mul(&self, other: Mat3) -> Mat3 { + Mat3 { + a: self.a*other.a, b: self.b*other.b, c: self.c*other.c, + d: self.d*other.d, e: self.e*other.e, f: self.f*other.f, + g: self.g*other.g, h: self.h*other.h, i: self.i*other.i, + } + } +} + +impl Add for Mat3 { + type Output = Mat3; + + fn add(self, other: Mat3) -> Mat3 { + Mat3 { + a: self.a+other.a, b: self.b+other.b, c: self.c+other.c, + d: self.d+other.d, e: self.e+other.e, f: self.f+other.f, + g: self.g+other.g, h: self.h+other.h, i: self.i+other.i, + } + } +} + +impl Sub for Mat3 { + type Output = Mat3; + + fn sub(self, other: Mat3) -> Mat3 { + Mat3 { + a: self.a-other.a, b: self.b-other.b, c: self.c-other.c, + d: self.d-other.d, e: self.e-other.e, f: self.f-other.f, + g: self.g-other.g, h: self.h-other.h, i: self.i-other.i, + } + } +} + +impl Mul for Mat3 { + type Output = Mat3; + + fn mul(self, other: Mat3) -> Mat3 { + Mat3 { + a: self.a*other.a + self.b*other.d + self.c*other.g, + b: self.a*other.b + self.b*other.e + self.c*other.h, + c: self.a*other.c + self.b*other.f + self.c*other.i, + + d: self.d*other.a + self.e*other.d + self.f*other.g, + e: self.d*other.b + self.e*other.e + self.f*other.h, + f: self.d*other.c + self.e*other.f + self.e*other.i, + + g: self.g*other.a + self.h*other.d + self.i*other.g, + h: self.g*other.b + self.h*other.e + self.i*other.h, + i: self.g*other.c + self.h*other.f + self.i*other.i, + } + } +} + +impl Neg for Mat3 { + type Output = Mat3; + + fn neg(self) -> Mat3 { + Mat3 { + a: -self.a, b: -self.b, c: -self.c, + d: -self.d, e: -self.e, f: -self.f, + g: -self.h, h: -self.h, i: -self.i, + } + } +} + +impl Mul for Mat3 { + type Output = Mat3; + + fn mul(self, k: f64) -> Mat3 { + Mat3 { + a: self.a*k, b: self.b*k, c: self.c*k, + d: self.d*k, e: self.e*k, f: self.f*k, + g: self.g*k, h: self.g*k, i: self.i*k, + } + } +} + +impl Mul for Mat3 { + type Output = Vec3; + + fn mul(self, other: Vec3) -> Vec3 { + let (x, y, z) = (other.x(), other.y(), other.z()); + let (a, b, c) = (self.a(), self.b(), self.c()); + let (d, e, f) = (self.d(), self.e(), self.f()); + let (g, h, i) = (self.g(), self.h(), self.i()); + + Vec3::new( + a*x + b*y + c*z, + d*x + e*y + f*z, + g*x + h*y + i*z, + ) + } +} + +impl Mul for Vec3 { + type Output = Vec3; + + fn mul(self, other: Mat3) -> Vec3 { + let (x, y, z) = (self.x(), self.y(), self.z()); + let (a, b, c) = (other.a(), other.b(), other.c()); + let (d, e, f) = (other.d(), other.e(), other.f()); + let (g, h, i) = (other.g(), other.h(), other.i()); + + Vec3::new( + x*a + y*d + z*g, + x*b + y*e + z*h, + x*c + y*f + z*i, + ) + } +} + +impl Div for Mat3 { + type Output = Mat3; + + fn div(self, k: f64) -> Mat3 { + Mat3 { + a: self.a/k, b: self.b/k, c: self.c/k, + d: self.d/k, e: self.e/k, f: self.f/k, + g: self.g/k, h: self.h/k, i: self.i/k, + } + } +} \ No newline at end of file diff --git a/src/structs/mat_n.rs b/src/structs/mat_n.rs new file mode 100644 index 0000000..0ff11a6 --- /dev/null +++ b/src/structs/mat_n.rs @@ -0,0 +1,133 @@ +use crate::structs::vec_n::Vec; +use std::ops::{Add, Sub, Neg, Mul, Div}; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Mat { + v: [[f64; N]; N], +} + +impl Mat { + pub const fn new(v: [[f64; N]; N]) -> Mat { + Mat { v } + } + + pub const fn new_zero() -> Mat { + Mat { v: [[0.; N]; N] } + } + + pub const fn new_unit() -> Mat { + let mut v = [[0.; N]; N]; + let mut i = 0; + while i Mat { + let mut v = [[0.; N]; N]; + v[i][j] = 1.; + Mat { v } + } + + pub const fn new_from_diagonal(d: Vec) -> Mat { + let mut v = [[0.; N]; N]; + let mut i = 0; + while i f64 { + self.v[i][j] + } +} + +impl Add for Mat { + type Output = Mat; + + fn add(self, other: Mat) -> Mat { + let mut v = [[0.; N]; N]; + for i in 0..N { + for j in 0..N { + v[i][j] = self.v[i][j] + other.v[i][j]; + } + } + Mat { v } + } +} + +impl Sub for Mat { + type Output = Mat; + + fn sub(self, other: Mat) -> Mat { + let mut v = [[0.; N]; N]; + for i in 0..N { + for j in 0..N { + v[i][j] = self.v[i][j] - other.v[i][j]; + } + } + Mat { v } + } +} + +impl Neg for Mat { + type Output = Mat; + + fn neg(self) -> Mat { + let mut v = [[0.; N]; N]; + for i in 0..N { + for j in 0..N { + v[i][j] = -self.v[i][j]; + } + } + Mat { v } + } +} + +impl Mul for Mat { + type Output = Mat; + + fn mul(self, k: f64) -> Mat { + let mut v = [[0.; N]; N]; + for i in 0..N { + for j in 0..N { + v[i][j] = self.v[i][j] * k; + } + } + Mat { v } + } +} + +impl Mul> for Mat { + type Output = Vec; + + fn mul(self, other: Vec) -> Vec { + let mut v = [0.; N]; + for i in 0..N { + let mut x = 0.; + for j in 0..N { + x += self.v[i][j] * other.v(i); + } + v[i] = x; + } + Vec::new(v) + } +} + +impl Div for Mat { + type Output = Mat; + + fn div(self, k: f64) -> Mat { + let mut v = [[0.; N]; N]; + for i in 0..N { + for j in 0..N { + v[i][j] = self.v[i][j] / k; + } + } + Mat { v } + } +} \ No newline at end of file diff --git a/src/structs/mod.rs b/src/structs/mod.rs new file mode 100644 index 0000000..eb74d22 --- /dev/null +++ b/src/structs/mod.rs @@ -0,0 +1,20 @@ +use crate::consts::COLOR_CHANNELS; + +mod vec3; +mod mat3; +mod vec_n; +mod mat_n; + +mod ray; +mod cam; + +pub use vec3::{Vec3, X, Y, Z, O}; +pub use mat3::{Mat3, I3, O3}; +pub use vec_n::Vec; +pub use mat_n::Mat; +pub use ray::Ray; +pub use cam::{Cam, Image}; + +pub type ColorVec = Vec; +pub const COLOR_ZERO: ColorVec = ColorVec::new_zero(); +pub type ColorMat = Mat; diff --git a/src/structs/ray.rs b/src/structs/ray.rs new file mode 100644 index 0000000..449940d --- /dev/null +++ b/src/structs/ray.rs @@ -0,0 +1,28 @@ +use crate::structs::Vec3; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Ray { + from: Vec3, + dir: Vec3, +} + +impl Ray { + pub fn new(from: Vec3, dir: Vec3) -> Ray { + Ray { from, dir } + } + + pub fn new_from_to(from: Vec3, to: Vec3) -> Ray { + Ray { from, dir: to - from } + } + + pub fn unit(&self) -> Ray { + Ray { from: self.from, dir: self.dir.unit() } + } + + pub fn from(&self) -> Vec3 { self.from } + pub fn dir(&self) -> Vec3 { self.dir } + + pub fn point(&self, steps: f64) -> Vec3 { + self.from + self.dir * steps + } +} \ No newline at end of file diff --git a/src/structs/vec3.rs b/src/structs/vec3.rs new file mode 100644 index 0000000..a9ac739 --- /dev/null +++ b/src/structs/vec3.rs @@ -0,0 +1,127 @@ +use std::ops::{Add, Sub, Neg, Mul, Div, BitXor}; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Vec3 { + x: f64, + y: f64, + z: f64, +} + +pub const X: Vec3 = Vec3 { x: 1., y: 0., z: 0. }; +pub const Y: Vec3 = Vec3 { x: 0., y: 1., z: 0. }; +pub const Z: Vec3 = Vec3 { x: 0., y: 0., z: 1. }; +pub const O: Vec3 = Vec3 { x: 0., y: 0., z: 0. }; + +impl Vec3 { + pub const fn new(x: f64, y: f64, z: f64) -> Vec3 { + Vec3 { x, y, z } + } + + pub const fn x(&self) -> f64 { self.x } + pub const fn y(&self) -> f64 { self.y } + pub const fn z(&self) -> f64 { self.z } + + pub fn magnitude(self) -> f64 { + f64::sqrt(self.x*self.x + self.y*self.y + self.z*self.z) + } + + pub fn unit(self) -> Vec3 { + self / self.magnitude() + } + + pub fn distance_to(self, other: Vec3) -> f64 { + (self - other).magnitude() + } + + pub fn angle_to(self, other: Vec3) -> f64 { + ((self * other) / (self.magnitude() * other.magnitude())).acos() + } + + pub fn member_mul(self, other: Vec3) -> Vec3 { + Vec3 { + x: self.x*other.x, + y: self.y*other.y, + z: self.z*other.z, + } + } +} + +impl Add for Vec3 { + type Output = Vec3; + + fn add(self, other: Vec3) -> Vec3 { + Vec3 { + x: self.x + other.x, + y: self.y + other.y, + z: self.z + other.z, + } + } +} + +impl Sub for Vec3 { + type Output = Vec3; + + fn sub(self, other: Vec3) -> Vec3 { + Vec3 { + x: self.x - other.x, + y: self.y - other.y, + z: self.z - other.z, + } + } +} + +impl Neg for Vec3 { + type Output = Vec3; + + fn neg(self) -> Vec3 { + Vec3 { + x: -self.x, + y: -self.y, + z: -self.z, + } + } +} + +impl BitXor for Vec3 { + type Output = Vec3; + + fn bitxor(self, other: Vec3) -> Vec3 { + Vec3 { + x: self.y * other.z - self.z * other.y, + y: self.z * other.x - self.x * other.z, + z: self.x * other.y - self.y * other.x, + } + } +} + +impl Mul for Vec3 { + type Output = f64; + + fn mul(self, other: Vec3) -> f64 { + self.x*other.x + self.y*other.y + self.z*other.z + } +} + +impl Mul for Vec3 { + type Output = Vec3; + + fn mul(self, k: f64) -> Vec3 { + Vec3 { + x: self.x * k, + y: self.y * k, + z: self.z * k, + } + } +} + +impl Div for Vec3 { + type Output = Vec3; + + fn div(self, k: f64) -> Vec3 { + Vec3 { + x: self.x / k, + y: self.y / k, + z: self.z / k, + } + } +} \ No newline at end of file diff --git a/src/structs/vec_n.rs b/src/structs/vec_n.rs new file mode 100644 index 0000000..1b5a159 --- /dev/null +++ b/src/structs/vec_n.rs @@ -0,0 +1,111 @@ +use std::ops::{Add, Sub, Neg, Mul, Div}; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct Vec { + v: [f64; N], +} + +impl Vec { + pub const fn new(v: [f64; N]) -> Vec { + Vec { v } + } + + pub const fn new_zero() -> Vec { + Vec { v: [0.; N] } + } + + pub const fn new_one(i: usize) -> Vec { + let mut v = [0.; N]; + v[i] = 1.; + Vec { v } + } + + pub const fn v(&self, i: usize) -> f64 { + self.v[i] + } + + pub fn magnitude(self) -> f64 { + let mut x = 0.; + for i in 0..N { + let v = self.v[i]; + x += v*v; + } + f64::sqrt(x) + } + + pub fn unit(self) -> Vec { + self / self.magnitude() + } + + pub fn distance_to(self, other: Vec) -> f64 { + (self - other).magnitude() + } + + pub fn member_mul(self, other: Vec) -> Vec { + let mut v = [0.; N]; + for i in 0..N { + v[i] = self.v[i] * other.v[i]; + } + Vec { v } + } +} + +impl Add for Vec { + type Output = Vec; + + fn add(self, other: Vec) -> Vec { + let mut v = [0.; N]; + for i in 0..N { + v[i] = self.v[i] + other.v[i]; + } + Vec { v } + } +} + +impl Sub for Vec { + type Output = Vec; + + fn sub(self, other: Vec) -> Vec { + let mut v = [0.; N]; + for i in 0..N { + v[i] = self.v[i] - other.v[i]; + } + Vec { v } + } +} + +impl Neg for Vec { + type Output = Vec; + + fn neg(self) -> Vec { + let mut v = [0.; N]; + for i in 0..N { + v[i] = -self.v[i]; + } + Vec { v } + } +} + +impl Mul for Vec { + type Output = Vec; + + fn mul(self, k: f64) -> Vec { + let mut v = [0.; N]; + for i in 0..N { + v[i] = self.v[i] * k; + } + Vec { v } + } +} + +impl Div for Vec { + type Output = Vec; + + fn div(self, k: f64) -> Vec { + let mut v = [0.; N]; + for i in 0..N { + v[i] = self.v[i] / k; + } + Vec { v } + } +} diff --git a/test b/test new file mode 100755 index 0000000..b6daf5b --- /dev/null +++ b/test @@ -0,0 +1,7 @@ +#!/bin/bash +clear +cargo build --release && \ + clear && \ + time target/release/rmarcher && \ + printf '\n' && \ + kitty +kitten icat --align=left a.png