initial commit

main
Codinget 4 years ago
commit a715710dd2
  1. 4
      .gitignore
  2. 455
      Cargo.lock
  3. 17
      Cargo.toml
  4. 43
      src/consts.rs
  5. 16
      src/light.rs
  6. 84
      src/main.rs
  7. 88
      src/material.rs
  8. 65
      src/object/cuboid.rs
  9. 23
      src/object/cylinder.rs
  10. 45
      src/object/intersection.rs
  11. 71
      src/object/mod.rs
  12. 31
      src/object/plane.rs
  13. 31
      src/object/scene.rs
  14. 27
      src/object/sphere.rs
  15. 34
      src/object/torus.rs
  16. 83
      src/object/transform.rs
  17. 45
      src/object/union.rs
  18. 22
      src/object/waves.rs
  19. 44
      src/object/with_lights.rs
  20. 64
      src/object/with_material.rs
  21. 323
      src/structs/cam.rs
  22. 217
      src/structs/mat3.rs
  23. 133
      src/structs/mat_n.rs
  24. 20
      src/structs/mod.rs
  25. 28
      src/structs/ray.rs
  26. 127
      src/structs/vec3.rs
  27. 111
      src/structs/vec_n.rs
  28. 7
      test

4
.gitignore vendored

@ -0,0 +1,4 @@
/target
/.idea
/rmarcher.iml
/a.*

455
Cargo.lock generated

@ -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"

@ -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

@ -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;

@ -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 }
}

@ -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();
}

@ -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
);

@ -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()<self.radius.x() && dy.abs()<self.radius.y() && dz.abs()<self.radius.z() {
-f64::min(
f64::min(
f64::min(
f64::abs(dx-self.radius.x()),
f64::abs(dx+self.radius.x())
),
f64::abs(dy-self.radius.y())
),
f64::min(
f64::min(
f64::abs(dy+self.radius.y()),
f64::abs(dz-self.radius.z())
),
f64::abs(dz+self.radius.z())
)
)
} else {
let dx0 = f64::max(0., dx.abs()-self.radius.x());
let dy0 = f64::max(0., dy.abs()-self.radius.y());
let dz0 = f64::max(0., dz.abs()-self.radius.z());
f64::sqrt(dx0*dx0 + dy0*dy0 + dz0*dz0)
}
}
fn normal_at(&self, point: Vec3) -> 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. },
)
}
}

@ -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())
}
}

@ -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: Obj, B: Obj> {
a: A,
b: B
}
impl<A: Obj, B: Obj> Intersection<A, B> {
pub fn new(a: A, b: B) -> Intersection<A, B> {
Intersection { a, b }
}
}
impl<A: Obj, B: Obj> Obj for Intersection<A, B> {
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<Light> {
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
}
}

@ -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<Light> {
Vec::new()
}
fn node_count(&self) -> u32 { 1 }
}
impl<T: Obj> 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<Light> { (*self).get_lights() }
fn node_count(&self) -> u32 { (*self).node_count() }
}
pub trait ObjClone: Obj {
fn clone_obj(&self) -> Box<dyn ObjClone>;
}
impl<T: 'static + Obj + Clone> ObjClone for T {
fn clone_obj(&self) -> Box<dyn ObjClone> {
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;

@ -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
}
}

@ -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<dyn ObjClone>
}
impl Scene {
pub fn new<T: 'static + ObjClone>(scene: T) -> Scene {
Scene { scene: Box::new(scene) }
}
pub fn new_from_box(scene: Box<dyn ObjClone>) -> 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<Light> { 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() }
}
}

@ -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()
}
}

@ -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()
}
}

@ -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<T: Obj + Clone> {
obj: T,
transform: Mat3,
transform_inv: Mat3,
translate: Vec3
}
impl<T: Obj + Clone> AffineTransform<T> {
pub fn new(obj: T, transform: Mat3, translate: Vec3) -> AffineTransform<T> {
AffineTransform { obj, transform, transform_inv: transform.invert(), translate }
}
pub fn new_linear(obj: T, transform: Mat3) -> AffineTransform<T> {
AffineTransform { obj, transform, transform_inv: transform.invert(), translate: O }
}
pub fn new_translate(obj: T, translate: Vec3) -> AffineTransform<T> {
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<T: Obj + Clone> Obj for AffineTransform<T> {
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<Light> {
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
}
}

@ -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: Obj, B: Obj> {
a: A,
b: B
}
impl<A: Obj, B: Obj> Union<A, B> {
pub fn new(a: A, b: B) -> Union<A, B> {
Union { a, b }
}
}
impl<A: Obj, B: Obj> Obj for Union<A, B> {
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<Light> {
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
}
}

@ -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()
}
}

@ -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<T: Obj, const N: usize> {
obj: T,
lights: [Light; N]
}
impl<T: Obj, const N: usize> WithLights<T, N> {
pub fn new(obj: T, lights: [Light; N]) -> WithLights<T, N> {
WithLights { obj, lights }
}
}
impl<T: Obj, const N: usize> Obj for WithLights<T, N> {
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<Light> {
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<T> = WithLights<T, 1>;
impl<T: Obj> WithLight<T> {
pub fn new_one(obj: T, light: Light) -> WithLight<T> {
WithLight { obj, lights: [light; 1] }
}
}

@ -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<T: Obj> {
obj: T,
material: Material
}
impl<T: Obj> WithMaterial<T> {
pub fn new(obj: T, material: Material) -> WithMaterial<T> {
WithMaterial { obj, material }
}
}
impl<T: Obj> Obj for WithMaterial<T> {
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<Light> {
self.obj.get_lights()
}
fn node_count(&self) -> u32 {
self.obj.node_count() + 1
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct WithDynamicMaterial<T: Obj, F: Clone + Send + Sync + Fn(Vec3) -> Material> {
obj: T,
fun: F
}
impl<T: Obj, F: Clone + Send + Sync + Fn(Vec3) -> Material> WithDynamicMaterial<T, F> {
pub fn new(obj: T, fun: F) -> WithDynamicMaterial<T, F> {
WithDynamicMaterial { obj, fun }
}
}
impl<T: Obj, F: Clone + Send + Sync + Fn(Vec3) -> Material> Obj for WithDynamicMaterial<T, F> {
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<Light> {
self.obj.get_lights()
}
fn node_count(&self) -> u32 {
self.obj.node_count() + 1
}
}

@ -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<RayData>; 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::<f64>() * (b-a) + a
}
fn shoot_ray<T: Obj>(scene: &T, pos: Vec3, dir: Vec3) -> Option<RayData> {
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<T: Obj>(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<T: Obj + Clone>(&self, scene: &T) -> Image {
if THREAD_COUNT > 1 {
self.render_multithreaded(scene)
} else {
self.render_singlethreaded(scene)
}
}
pub fn render_singlethreaded<T: Obj>(&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<T: Obj + Clone>(&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::<Slice>();
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<T: Obj>(&self, scene: &T, lights: &Vec<Light>, 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<Light>, 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<T: Obj>(&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<Light>, pos: Vec3) -> Vec<LightRayData<'a>> {
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()
}
}

@ -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<Mat3> 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<f64> 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<Vec3> 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<Mat3> 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<f64> 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,
}
}
}

@ -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<const N: usize> {
v: [[f64; N]; N],
}
impl<const N: usize> Mat<N> {
pub const fn new(v: [[f64; N]; N]) -> Mat<N> {
Mat { v }
}
pub const fn new_zero() -> Mat<N> {
Mat { v: [[0.; N]; N] }
}
pub const fn new_unit() -> Mat<N> {
let mut v = [[0.; N]; N];
let mut i = 0;
while i<N {
v[i][i] = 1.;
i += 1;
}
Mat { v }
}
pub const fn new_one(i: usize, j: usize) -> Mat<N> {
let mut v = [[0.; N]; N];
v[i][j] = 1.;
Mat { v }
}
pub const fn new_from_diagonal(d: Vec<N>) -> Mat<N> {
let mut v = [[0.; N]; N];
let mut i = 0;
while i<N {
v[i][i] = d.v(i);
i += 1;
}
Mat { v }
}
pub const fn v(&self, i: usize, j: usize) -> f64 {
self.v[i][j]
}
}
impl<const N: usize> Add for Mat<N> {
type Output = Mat<N>;
fn add(self, other: Mat<N>) -> Mat<N> {
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<const N: usize> Sub for Mat<N> {
type Output = Mat<N>;
fn sub(self, other: Mat<N>) -> Mat<N> {
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<const N: usize> Neg for Mat<N> {
type Output = Mat<N>;
fn neg(self) -> Mat<N> {
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<const N: usize> Mul<f64> for Mat<N> {
type Output = Mat<N>;
fn mul(self, k: f64) -> Mat<N> {
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<const N: usize> Mul<Vec<N>> for Mat<N> {
type Output = Vec<N>;
fn mul(self, other: Vec<N>) -> Vec<N> {
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<const N: usize> Div<f64> for Mat<N> {
type Output = Mat<N>;
fn div(self, k: f64) -> Mat<N> {
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 }
}
}

@ -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<COLOR_CHANNELS>;
pub const COLOR_ZERO: ColorVec = ColorVec::new_zero();
pub type ColorMat = Mat<COLOR_CHANNELS>;

@ -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
}
}

@ -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<Vec3> 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<f64> 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<f64> for Vec3 {
type Output = Vec3;
fn div(self, k: f64) -> Vec3 {
Vec3 {
x: self.x / k,
y: self.y / k,
z: self.z / k,
}
}
}

@ -0,0 +1,111 @@
use std::ops::{Add, Sub, Neg, Mul, Div};
#[derive(Debug, Copy, Clone, PartialEq)]
pub struct Vec<const N: usize> {
v: [f64; N],
}
impl<const N: usize> Vec<N> {
pub const fn new(v: [f64; N]) -> Vec<N> {
Vec { v }
}
pub const fn new_zero() -> Vec<N> {
Vec { v: [0.; N] }
}
pub const fn new_one(i: usize) -> Vec<N> {
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<N> {
self / self.magnitude()
}
pub fn distance_to(self, other: Vec<N>) -> f64 {
(self - other).magnitude()
}
pub fn member_mul(self, other: Vec<N>) -> Vec<N> {
let mut v = [0.; N];
for i in 0..N {
v[i] = self.v[i] * other.v[i];
}
Vec { v }
}
}
impl<const N: usize> Add for Vec<N> {
type Output = Vec<N>;
fn add(self, other: Vec<N>) -> Vec<N> {
let mut v = [0.; N];
for i in 0..N {
v[i] = self.v[i] + other.v[i];
}
Vec { v }
}
}
impl<const N: usize> Sub for Vec<N> {
type Output = Vec<N>;
fn sub(self, other: Vec<N>) -> Vec<N> {
let mut v = [0.; N];
for i in 0..N {
v[i] = self.v[i] - other.v[i];
}
Vec { v }
}
}
impl<const N: usize> Neg for Vec<N> {
type Output = Vec<N>;
fn neg(self) -> Vec<N> {
let mut v = [0.; N];
for i in 0..N {
v[i] = -self.v[i];
}
Vec { v }
}
}
impl<const N: usize> Mul<f64> for Vec<N> {
type Output = Vec<N>;
fn mul(self, k: f64) -> Vec<N> {
let mut v = [0.; N];
for i in 0..N {
v[i] = self.v[i] * k;
}
Vec { v }
}
}
impl<const N: usize> Div<f64> for Vec<N> {
type Output = Vec<N>;
fn div(self, k: f64) -> Vec<N> {
let mut v = [0.; N];
for i in 0..N {
v[i] = self.v[i] / k;
}
Vec { v }
}
}

@ -0,0 +1,7 @@
#!/bin/bash
clear
cargo build --release && \
clear && \
time target/release/rmarcher && \
printf '\n' && \
kitty +kitten icat --align=left a.png
Loading…
Cancel
Save