├── .github └── workflows │ └── rust.yml ├── .gitignore ├── .rust-toolchain ├── Cargo.lock ├── Cargo.toml ├── LICENSE.md ├── README.md ├── docs ├── bit-later-render.png ├── first-render.png └── procedural-fractal-gallery.png ├── scenes ├── no-material-sphere.json ├── procedural-texture-gallery.json ├── rgb-spheres.json └── rlogo.json └── src ├── camera.rs ├── color.rs ├── config ├── camera.rs ├── light.rs ├── material.rs ├── mod.rs ├── object.rs ├── scene.rs └── transform.rs ├── config_loader.rs ├── geometry ├── aabb.rs ├── extent_volume.rs ├── instance.rs ├── mesh.rs ├── mod.rs ├── octtree.rs ├── plane.rs ├── simple_triangle_storage.rs ├── sphere.rs └── triangle.rs ├── intersection.rs ├── lib.rs ├── light ├── directional.rs ├── mod.rs └── point.rs ├── main.rs ├── material.rs ├── math ├── complex.rs ├── matrix4.rs ├── mod.rs ├── three_dimensions.rs ├── transform.rs └── two_dimensions.rs ├── mesh_loader.rs ├── ray.rs ├── renderer.rs ├── scene.rs └── texture ├── file.rs ├── mod.rs ├── procedural ├── checkerboard.rs ├── julia.rs ├── mandelbrot.rs └── mod.rs └── solid.rs /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - uses: actions/checkout@v2 16 | - name: Build 17 | run: cargo build --verbose 18 | - name: Run tests 19 | run: cargo test --verbose 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | images/ 3 | *.obj 4 | *.mtl 5 | scenes/*/ 6 | .DS_Store 7 | -------------------------------------------------------------------------------- /.rust-toolchain: -------------------------------------------------------------------------------- 1 | stable 2 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rusttracer" 3 | version = "0.1.0" 4 | authors = ["Hugo Tunius ", "Andrew Aylett "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | image = "0.25" 9 | rayon = "1" 10 | tobj = "4.0.0" 11 | serde = { version = "1", features = ["derive"] } 12 | serde_json = "1.0" 13 | getopts = "0.2.4" 14 | tracing = "0.1.40" 15 | tracing-subscriber = "0.3.18" 16 | 17 | [features] 18 | default = [] 19 | 20 | stats = [] 21 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Hugo Tunius 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rusttracer 2 | 3 | ![Rust](https://github.com/k0nserv/rusttracer/workflows/Rust/badge.svg) 4 | 5 | YAR(Yet Another Raytracer). 6 | 7 | I like to write raytracers to learn new programming languages. They're fun and are a natural way to learn most OOP languages. Naturally I'm writing a raytracer in rust called rusttracer 🙂. 8 | 9 | ## Building 10 | 11 | Make sure you have the rust nightly toolchain installed then run 12 | 13 | ```bash 14 | cargo build --release 15 | ``` 16 | 17 | ## Running 18 | 19 | The project uses a json based configuration format. For an example see the [`rgb-spheres.json`](scenes/rgb-spheres.json) scene. To render it run: 20 | 21 | ```bash 22 | target/release/rusttracer --config-path scenes/rgb-spheres.json 23 | ``` 24 | 25 | ## Usage 26 | 27 | ```bash 28 | Usage: target/release/rusttracer [options] 29 | 30 | Options: 31 | -c, --config-path CONFIG_PATH 32 | config file path, uses `default.json` if not specified 33 | -b, --benchmark Benchmark by rendering the scene multiple times 34 | -h, --help prints this help menu 35 | ``` 36 | 37 | ## Renders 38 | 39 | 40 | ![](docs/first-render.png) 41 | > This was the first render produced. 42 | 43 | 44 | ![](docs/bit-later-render.png) 45 | > This was rendered a while later, at this point there was support for diffuse colors, specular highlights, reflection, colored lights, and super sampling. This is 2560x1440 at 4x4 super sampling 46 | 47 | ![](docs/procedural-fractal-gallery.png) 48 | > This rendering uses procedural textures to render the fractal artworks. This is 2560x1440 at 4x4 super sampling. 49 | 50 | -------------------------------------------------------------------------------- /docs/bit-later-render.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k0nserv/rusttracer/848de68a76fee7a02e6d280572f3147cc9726368/docs/bit-later-render.png -------------------------------------------------------------------------------- /docs/first-render.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k0nserv/rusttracer/848de68a76fee7a02e6d280572f3147cc9726368/docs/first-render.png -------------------------------------------------------------------------------- /docs/procedural-fractal-gallery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/k0nserv/rusttracer/848de68a76fee7a02e6d280572f3147cc9726368/docs/procedural-fractal-gallery.png -------------------------------------------------------------------------------- /scenes/no-material-sphere.json: -------------------------------------------------------------------------------- 1 | { 2 | "max_depth": 5, 3 | "super_sampling": { 4 | "On": 4 5 | }, 6 | "cameras": [{ 7 | "fov": 0.873, 8 | "width": 2560, 9 | "height": 1440, 10 | "position": [25.0, 5.0, -5.0], 11 | "look_at": [0.0, 3.5, -25.0], 12 | "up": [0.0, 1.0, 0.0] 13 | }], 14 | "scenes": [{ 15 | "clear_color": [0.0, 0.0, 0.0], 16 | "ambient_color": [1.0, 1.0, 1.0], 17 | "objects": [{ 18 | "type": "Plane", 19 | "normal": [0.0, 1.0, 0.0], 20 | "material_name": "wall" 21 | }, { 22 | "type": "Plane", 23 | "normal": [0.0, 0.0, 1.0], 24 | "material_name": "wall", 25 | "transforms": [{ 26 | "type": "Translate", 27 | "value": [0.0, 0.0, -50.0] 28 | }] 29 | }, { 30 | "type": "Plane", 31 | "normal": [-1.0, 0.0, 0.0], 32 | "material_name": "wall", 33 | "transforms": [{ 34 | "type": "Translate", 35 | "value": [50.0, 0.0, 0.0] 36 | }] 37 | }, { 38 | "type": "Plane", 39 | "normal": [1.0, 0.0, 0.0], 40 | "material_name": "wall", 41 | "transforms": [{ 42 | "type": "Translate", 43 | "value": [-50.0, 0.0, 0.0] 44 | }] 45 | }, { 46 | "type": "Sphere", 47 | "radius": 3.0, 48 | "transforms": [{ 49 | "type": "Translate", 50 | "value": [0.0, 3.5, -25.0] 51 | }] 52 | }], 53 | "lights": [{ 54 | "type": "PointLight", 55 | "origin": [0.0, 12.5, -15.0], 56 | "color": [1.0, 1.0, 1.0], 57 | "intensity": 80.0 58 | }] 59 | }], 60 | "materials": [{ 61 | "name": "wall", 62 | "ambient_color": [0.1, 0.1, 0.1], 63 | "diffuse_color": [1.0, 1.0, 1.0], 64 | "specular_color": [0.0, 0.0, 0.0], 65 | "specular_exponent": 0, 66 | "illumination_model": "Diffuse" 67 | }] 68 | } 69 | -------------------------------------------------------------------------------- /scenes/procedural-texture-gallery.json: -------------------------------------------------------------------------------- 1 | { 2 | "max_depth": 10, 3 | "super_sampling": { 4 | "On": 4 5 | }, 6 | "cameras": [{ 7 | "fov": 1.570796, 8 | "width": 2560, 9 | "height": 1440, 10 | "position": [0.0, 5.0, -15.0], 11 | "look_at": [0.0, 5.0, -35.0], 12 | "up": [0.0, 1.0, 0.0] 13 | }], 14 | "scenes": [{ 15 | "clear_color": [0.0, 0.0, 0.0], 16 | "ambient_color": [0.2, 0.2, 0.2], 17 | "objects": [{ 18 | "type": "Mesh", 19 | "path": "wall.obj", 20 | "material_name": "mandelbrot", 21 | "transforms": [ { 22 | "type": "Scale", 23 | "value": [3.0, 3.0, 3.0] 24 | }, { 25 | "type": "Scale", 26 | "value": [3.1, 1.0, 2.4] 27 | }, { 28 | "type": "RotateX", 29 | "value": 1.570796327 30 | },{ 31 | "type": "Translate", 32 | "value": [-10.0, 10.0, -34.899] 33 | }] 34 | }, { 35 | "type": "Mesh", 36 | "path": "wall.obj", 37 | "material_name": "frame", 38 | "transforms": [ { 39 | "type": "Scale", 40 | "value": [3.2, 3.2, 3.2] 41 | }, { 42 | "type": "Scale", 43 | "value": [3.1, 1.0, 2.4] 44 | }, { 45 | "type": "RotateX", 46 | "value": 1.570796327 47 | },{ 48 | "type": "Translate", 49 | "value": [-10.0, 10.0, -34.9] 50 | }] 51 | }, { 52 | "type": "Mesh", 53 | "path": "wall.obj", 54 | "material_name": "julia", 55 | "transforms": [{ 56 | "type": "Scale", 57 | "value": [8.0, 8.0, 8.0] 58 | }, { 59 | "type": "RotateX", 60 | "value": 1.570796327 61 | },{ 62 | "type": "Translate", 63 | "value": [10.0, 10.0, -34.899] 64 | }] 65 | }, { 66 | "type": "Mesh", 67 | "path": "wall.obj", 68 | "material_name": "frame", 69 | "transforms": [{ 70 | "type": "Scale", 71 | "value": [8.5, 8.5, 8.5] 72 | }, { 73 | "type": "RotateX", 74 | "value": 1.570796327 75 | },{ 76 | "type": "Translate", 77 | "value": [10.0, 10.0, -34.9] 78 | }] 79 | }, { 80 | "type": "Plane", 81 | "normal": [0.0, 1.0, 0.0], 82 | "material_name": "mirror", 83 | "transforms": [{ 84 | "type": "Translate", 85 | "value": [0.0, 0.0, -5.0] 86 | }] 87 | }, { 88 | "type": "Plane", 89 | "normal": [0.0, 0.0, 1.0], 90 | "material_name": "wall", 91 | "transforms": [{ 92 | "type": "Translate", 93 | "value": [0.0, 0.0, -35.0] 94 | }] 95 | }, { 96 | "type": "Sphere", 97 | "radius": 1.5, 98 | "material_name": "glass_ball", 99 | "transforms": [{ 100 | "type": "Translate", 101 | "value": [3.0, 8.0, -25] 102 | }] 103 | 104 | }], 105 | "lights": [{ 106 | "type": "DirectionalLight", 107 | "direction": [0.0, -1.0, -0.1], 108 | "color": [1.0, 0.91372549, 0.682352941], 109 | "intensity": 0.6, 110 | "specular": false 111 | }, { 112 | "type": "DirectionalLight", 113 | "direction": [0.0, 0.0, -1.0], 114 | "color": [1.0, 0.91372549, 0.682352941], 115 | "intensity": 0.3, 116 | "specular": false 117 | }, { 118 | "type": "PointLight", 119 | "origin": [1.0, 30.0, -15.0], 120 | "color": [1.0, 1.0, 1.0], 121 | "intensity": 200.0 122 | }] 123 | }], 124 | "materials": [{ 125 | "name": "mandelbrot", 126 | "ambient_color": [0.0, 0.0, 0.0], 127 | "diffuse_color": [1.0, 1.0, 1.0], 128 | "diffuse_texture": { 129 | "name": "mandelbrot" 130 | }, 131 | "specular_color": [0.0, 0.0, 0.0], 132 | "specular_exponent": 0, 133 | "illumination_model": "Diffuse" 134 | }, { 135 | "name": "julia", 136 | "ambient_color": [0.0, 0.0, 0.0], 137 | "diffuse_color": [1.0, 1.0, 1.0], 138 | "diffuse_texture": { 139 | "name": "julia" 140 | }, 141 | "specular_color": [0.0, 0.0, 0.0], 142 | "specular_exponent": 0, 143 | "illumination_model": "Diffuse" 144 | }, { 145 | "name": "mirror", 146 | "ambient_color": [0.0, 0.0, 0.0], 147 | "diffuse_color": [0.188235294, 0.164705882, 0.11372549], 148 | "specular_color": [1.0, 1.0, 0.603921569], 149 | "specular_exponent": 1, 150 | "illumination_model": "DiffuseSpecularReflectiveGlass", 151 | "reflection_coefficient": 0.15 152 | }, { 153 | "name": "wall", 154 | "ambient_color": [0.1, 0.1, 0.1], 155 | "diffuse_color": [1.0, 1.0, 1.0], 156 | "specular_color": [0.0, 0.0, 0.0], 157 | "specular_exponent": 0, 158 | "illumination_model": "Diffuse" 159 | }, { 160 | "name": "frame", 161 | "ambient_color": [0.0, 0.0, 0.0], 162 | "diffuse_color": [0.160784314, 0.133333333, 0.133333333], 163 | "specular_color": [0.0, 0.0, 0.0], 164 | "specular_exponent": 0, 165 | "illumination_model": "Diffuse" 166 | }, { 167 | "name": "glass_ball", 168 | "ambient_color": [0.0, 0.0, 0.0], 169 | "diffuse_color": [0.1, 0.1, 0.1], 170 | "specular_color": [1.0, 1.0, 1.0], 171 | "specular_exponent": 1.5, 172 | "refraction_coefficient": 1.37, 173 | "reflection_coefficient": 0.2, 174 | "illumination_model": "DiffuseSpecularRefractedFresnel" 175 | }] 176 | } 177 | -------------------------------------------------------------------------------- /scenes/rgb-spheres.json: -------------------------------------------------------------------------------- 1 | { 2 | "max_depth": 5, 3 | "super_sampling": { 4 | "On": 4 5 | }, 6 | "cameras": [{ 7 | "fov": 0.873, 8 | "width": 2560, 9 | "height": 1440, 10 | "position": [0.0, 5.0, 0.0], 11 | "look_at": [0.0, 2.0, -30.0], 12 | "up": [0.0, 1.0, 0.0] 13 | }], 14 | "scenes": [{ 15 | "clear_color": [0.0, 0.0, 0.0], 16 | "ambient_color": [0.2, 0.2, 0.2], 17 | "objects": [{ 18 | "type": "Plane", 19 | "normal": [0.0, 1.0, 0.0], 20 | "material_name": "wall" 21 | }, { 22 | "type": "Plane", 23 | "normal": [0.0, 0.0, 1.0], 24 | "material_name": "wall", 25 | "transforms": [{ 26 | "type": "Translate", 27 | "value": [0.0, 0.0, -35.0] 28 | }] 29 | }, { 30 | "type": "Sphere", 31 | "radius": 1.5, 32 | "material_name": "green_sphere", 33 | "transforms": [{ 34 | "type": "Translate", 35 | "value": [0.0, 2.0, -30.0] 36 | }] 37 | }, { 38 | "type": "Sphere", 39 | "radius": 1.5, 40 | "material_name": "blue_sphere", 41 | "transforms": [{ 42 | "type": "Translate", 43 | "value": [15.0, 2.0, -30.0] 44 | }] 45 | }, { 46 | "type": "Sphere", 47 | "radius": 1.5, 48 | "material_name": "red_sphere", 49 | "transforms": [{ 50 | "type": "Translate", 51 | "value": [-15.0, 2.0, -30.0] 52 | }] 53 | }], 54 | "lights": [{ 55 | "type": "PointLight", 56 | "origin": [0.0, 15.0, -20.0], 57 | "color": [1.0, 1.0, 1.0], 58 | "intensity": 100.0 59 | }, { 60 | "type": "PointLight", 61 | "origin": [0.0, 8.0, -30.0], 62 | "color": [0.262745098, 0.976470588, 0.992156863], 63 | "intensity": 5.0 64 | }, { 65 | "type": "PointLight", 66 | "origin": [-15.0, 8.0, -30.0], 67 | "color": [0.360784314, 0.992156863, 0.262745098], 68 | "intensity": 5.0 69 | }, { 70 | "type": "PointLight", 71 | "origin": [15.0, 8.0, -30.0], 72 | "color": [0.992156863, 0.450980392, 0.023529412], 73 | "intensity": 5.0 74 | }] 75 | }], 76 | "materials": [{ 77 | "name": "red_sphere", 78 | "ambient_color": [0.0, 0.0, 0.0], 79 | "diffuse_color": [0.86, 0.0, 0.31], 80 | "specular_color": [0.0, 0.0, 0.0], 81 | "specular_exponent": 0, 82 | "illumination_model": "Diffuse" 83 | }, { 84 | "name": "green_sphere", 85 | "ambient_color": [0.0, 0.0, 0.0], 86 | "diffuse_color": [0.0, 0.86, 0.31], 87 | "specular_color": [0.0, 0.0, 0.0], 88 | "specular_exponent": 0, 89 | "illumination_model": "Diffuse" 90 | }, { 91 | "name": "blue_sphere", 92 | "ambient_color": [0.0, 0.0, 0.0], 93 | "diffuse_color": [0.0, 0.31, 0.86], 94 | "specular_color": [0.0, 0.0, 0.0], 95 | "specular_exponent": 0, 96 | "illumination_model": "Diffuse" 97 | }, { 98 | "name": "wall", 99 | "ambient_color": [0.1, 0.1, 0.1], 100 | "diffuse_color": [1.0, 1.0, 1.0], 101 | "specular_color": [0.0, 0.0, 0.0], 102 | "specular_exponent": 0, 103 | "illumination_model": "Diffuse" 104 | }] 105 | } 106 | -------------------------------------------------------------------------------- /scenes/rlogo.json: -------------------------------------------------------------------------------- 1 | { 2 | "max_depth": 5, 3 | "super_sampling": "Off", 4 | "cameras": [{ 5 | "fov": 0.873, 6 | "width": 2560, 7 | "height": 1440, 8 | "position": [0.0, 2.0, 5.0], 9 | "look_at": [0.0, 0.5, 0.0], 10 | "up": [0.0, 1.0, 0.0] 11 | }], 12 | "scenes": [{ 13 | "clear_color": [0.0, 0.0, 0.0], 14 | "ambient_color": [0.0, 0.0, 0.0], 15 | "objects": [{ 16 | "type": "Mesh", 17 | "path": "rlogo.obj" 18 | }, { 19 | "type": "Plane", 20 | "normal": [0.0, 1.0, 0.0], 21 | "material_name": "wall", 22 | "transforms": [{ 23 | "type": "Translate", 24 | "value": [0.0, -2.0, 0.0] 25 | }] 26 | }], 27 | "lights": [{ 28 | "type": "PointLight", 29 | "origin": [3.0, 5.0, 6.0], 30 | "color": [1.0, 1.0, 1.0], 31 | "intensity": 50.0 32 | }, { 33 | "type": "PointLight", 34 | "origin": [-3.0, 5.0, 6.0], 35 | "color": [1.0, 1.0, 1.0], 36 | "intensity": 50.0 37 | }] 38 | }], 39 | "materials": [{ 40 | "name": "wall", 41 | "ambient_color": [0.0, 0.0, 0.0], 42 | "diffuse_color": [1.0, 1.0, 1.0], 43 | "specular_color": [0.0, 0.0, 0.0], 44 | "specular_exponent": 0, 45 | "illumination_model": "Diffuse" 46 | }] 47 | } 48 | -------------------------------------------------------------------------------- /src/camera.rs: -------------------------------------------------------------------------------- 1 | use crate::config; 2 | use crate::math::{Matrix4, Point3, Vector3}; 3 | use crate::ray::Ray; 4 | 5 | #[derive(Debug)] 6 | pub struct Camera { 7 | pub width: u32, 8 | pub height: u32, 9 | widthf: f32, 10 | heightf: f32, 11 | scale: f32, 12 | aspect_ratio: f32, 13 | camera_to_world: Matrix4, 14 | } 15 | 16 | impl Camera { 17 | pub fn new( 18 | fov: f32, 19 | width: u32, 20 | height: u32, 21 | position: Point3, 22 | look_at: Point3, 23 | tmp_up: Vector3, 24 | ) -> Self { 25 | let aspect_ratio = (width as f32) / (height as f32); 26 | let scale = (fov * 0.5).tan(); 27 | let direction = (position - look_at).normalize(); 28 | let right = tmp_up.normalize().cross(&direction); 29 | let up = direction.cross(&right); 30 | 31 | Self { 32 | width, 33 | height, 34 | widthf: (width as f32), 35 | heightf: (height as f32), 36 | scale, 37 | aspect_ratio, 38 | camera_to_world: Self::camera_to_world_matrix( 39 | right.normalize(), 40 | up.normalize(), 41 | direction, 42 | position, 43 | ), 44 | } 45 | } 46 | 47 | pub fn create_ray(&self, x: u32, y: u32, x_sample: u32, y_sample: u32, samples: u32) -> Ray { 48 | let samplesf = samples as f32; 49 | let sample_width = self.widthf * samplesf; 50 | let sample_height = self.heightf * samplesf; 51 | 52 | let x_sample_offset = if samples == 1 { 0.5 } else { x_sample as f32 }; 53 | let y_sample_offset = if samples == 1 { 0.5 } else { y_sample as f32 }; 54 | 55 | let px = ((2.0 * (((x * samples) as f32) + x_sample_offset) / sample_width) - 1.0) 56 | * self.aspect_ratio 57 | * self.scale; 58 | let py = 59 | ((2.0 * (((y * samples) as f32) + y_sample_offset) / sample_height) - 1.0) * self.scale; 60 | 61 | let direction = self.camera_to_world * Vector3::new(px, py, -1.0); 62 | let origin = self.camera_to_world * Point3::at_origin(); 63 | 64 | Ray::new(origin, direction.normalize(), None) 65 | } 66 | 67 | pub fn camera_to_world_matrix( 68 | right: Vector3, 69 | up: Vector3, 70 | direction: Vector3, 71 | position: Point3, 72 | ) -> Matrix4 { 73 | let mut result = Matrix4::identity(); 74 | 75 | // right 76 | result[(0, 0)] = right.x; 77 | result[(0, 1)] = right.y; 78 | result[(0, 2)] = right.z; 79 | 80 | // up 81 | result[(1, 0)] = up.x; 82 | result[(1, 1)] = up.y; 83 | result[(1, 2)] = up.z; 84 | 85 | // direction 86 | result[(2, 0)] = direction.x; 87 | result[(2, 1)] = direction.y; 88 | result[(2, 2)] = direction.z; 89 | 90 | // position 91 | result[(3, 0)] = position.x; 92 | result[(3, 1)] = position.y; 93 | result[(3, 2)] = position.z; 94 | 95 | result 96 | } 97 | } 98 | 99 | impl<'a> From<&'a config::Camera> for Camera { 100 | fn from(config: &config::Camera) -> Self { 101 | Self::new( 102 | config.fov, 103 | config.width, 104 | config.height, 105 | Point3::from(config.position), 106 | Point3::from(config.look_at), 107 | Vector3::from(config.up), 108 | ) 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/color.rs: -------------------------------------------------------------------------------- 1 | use std::fmt; 2 | use std::iter::Iterator; 3 | use std::ops::{Add, Mul, Sub}; 4 | 5 | #[derive(Debug, Copy, Clone)] 6 | pub struct Color { 7 | r: u8, 8 | g: u8, 9 | b: u8, 10 | next: NextColor, 11 | } 12 | 13 | #[derive(Debug, Copy, Clone)] 14 | pub enum NextColor { 15 | Red, 16 | Green, 17 | Blue, 18 | Done, 19 | } 20 | 21 | impl Iterator for Color { 22 | type Item = u8; 23 | 24 | fn next(&mut self) -> Option { 25 | match self.next { 26 | NextColor::Red => { 27 | self.next = NextColor::Green; 28 | Some(self.r) 29 | } 30 | NextColor::Green => { 31 | self.next = NextColor::Blue; 32 | Some(self.g) 33 | } 34 | NextColor::Blue => { 35 | self.next = NextColor::Done; 36 | Some(self.b) 37 | } 38 | NextColor::Done => None, 39 | } 40 | } 41 | } 42 | 43 | impl Color { 44 | pub fn new(r: u8, g: u8, b: u8) -> Color { 45 | Color { 46 | r, 47 | g, 48 | b, 49 | next: NextColor::Red, 50 | } 51 | } 52 | 53 | pub fn new_f32(r: f32, g: f32, b: f32) -> Color { 54 | Color::new( 55 | Color::clamp((r * 255.0) as i32), 56 | Color::clamp((g * 255.0) as i32), 57 | Color::clamp((b * 255.0) as i32), 58 | ) 59 | } 60 | 61 | pub fn new_hsv(hue: f32, saturation: f32, value: f32) -> Color { 62 | let normalized_hue = hue % 360.0; 63 | let normalized_saturation = saturation.min(1.0).max(0.0); 64 | let normalized_value = value.min(1.0).max(0.0); 65 | 66 | let chroma = normalized_saturation * normalized_value; 67 | let hue_d = normalized_hue / 60.0; 68 | let x = chroma * (1.0 - ((hue_d % 2.0) - 1.0).abs()); 69 | 70 | let color = match hue_d { 71 | v if (0.0..=1.0).contains(&v) => (chroma, x, 0.0), 72 | v if v > 1.0 && v <= 2.0 => (x, chroma, 0.0), 73 | v if v > 2.0 && v <= 3.0 => (0.0, chroma, x), 74 | v if v > 3.0 && v <= 4.0 => (0.0, x, chroma), 75 | v if v > 4.0 && v <= 5.0 => (x, 0.0, chroma), 76 | v if v > 5.0 && v <= 6.0 => (chroma, 0.0, x), 77 | _ => (0.0, 0.0, 0.0), 78 | }; 79 | 80 | let m = normalized_value - chroma; 81 | Color::new_f32(color.0 + m, color.1 + m, color.2 + m) 82 | } 83 | 84 | #[inline(always)] 85 | pub fn r(self) -> u8 { 86 | self.r 87 | } 88 | 89 | #[inline(always)] 90 | pub fn r_f32(self) -> f32 { 91 | f32::from(self.r()) / 255.0 92 | } 93 | 94 | #[inline(always)] 95 | pub fn g(self) -> u8 { 96 | self.g 97 | } 98 | 99 | #[inline(always)] 100 | pub fn g_f32(self) -> f32 { 101 | f32::from(self.g()) / 255.0 102 | } 103 | 104 | #[inline(always)] 105 | pub fn b(self) -> u8 { 106 | self.b 107 | } 108 | 109 | #[inline(always)] 110 | pub fn b_f32(self) -> f32 { 111 | f32::from(self.b()) / 255.0 112 | } 113 | 114 | #[inline(always)] 115 | pub fn as_u32(self) -> u32 { 116 | 0xFF00_0000 & u32::from(self.r) & u32::from(self.g) << 8 & u32::from(self.b) << 16 117 | } 118 | 119 | fn clamp(value: i32) -> u8 { 120 | match value { 121 | v if v < 0 => 0, 122 | v if v > i32::from(u8::max_value()) => u8::max_value(), 123 | v => v as u8, 124 | } 125 | } 126 | } 127 | 128 | impl fmt::Display for Color { 129 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 130 | write!(f, "(r: {}, g: {}, b: {})", self.r(), self.g(), self.b()) 131 | } 132 | } 133 | 134 | impl PartialEq for Color { 135 | fn eq(&self, other: &Color) -> bool { 136 | self.r == other.r && self.g == other.g && self.b == other.b 137 | } 138 | } 139 | 140 | impl Eq for Color {} 141 | 142 | impl From<[f32; 3]> for Color { 143 | fn from(values: [f32; 3]) -> Self { 144 | Self::new( 145 | Self::clamp((values[0] * 255.0) as i32), 146 | Self::clamp((values[1] * 255.0) as i32), 147 | Self::clamp((values[2] * 255.0) as i32), 148 | ) 149 | } 150 | } 151 | 152 | // Math 153 | 154 | impl Add for Color { 155 | type Output = Color; 156 | 157 | fn add(self, other: Color) -> Color { 158 | let f = |f: fn(Color) -> u8| Color::clamp(i32::from(f(self)) + i32::from(f(other))); 159 | 160 | Color::new(f(Color::r), f(Color::g), f(Color::b)) 161 | } 162 | } 163 | 164 | impl Sub for Color { 165 | type Output = Color; 166 | 167 | fn sub(self, other: Color) -> Color { 168 | let r = Color::clamp(i32::from(self.r()) - i32::from(other.r())); 169 | let g = Color::clamp(i32::from(self.g()) - i32::from(other.g())); 170 | let b = Color::clamp(i32::from(self.b()) - i32::from(other.b())); 171 | 172 | Color::new(r, g, b) 173 | } 174 | } 175 | 176 | impl Mul for Color { 177 | type Output = Color; 178 | 179 | fn mul(self, other: Color) -> Color { 180 | let r = self.r_f32() * other.r_f32(); 181 | let g = self.g_f32() * other.g_f32(); 182 | let b = self.b_f32() * other.b_f32(); 183 | 184 | Color::new_f32(r, g, b) 185 | } 186 | } 187 | 188 | impl Mul for Color { 189 | type Output = Color; 190 | 191 | fn mul(self, other: f32) -> Color { 192 | let r = self.r_f32() * other; 193 | let g = self.g_f32() * other; 194 | let b = self.b_f32() * other; 195 | 196 | Color::new_f32(r, g, b) 197 | } 198 | } 199 | 200 | // Factory methods for common colors 201 | macro_rules! define_color { 202 | ($name:ident, $r:expr, $g:expr, $b:expr) => { 203 | #[inline(always)] 204 | pub fn $name() -> Color { 205 | Color::new($r, $g, $b) 206 | } 207 | }; 208 | } 209 | 210 | impl Color { 211 | define_color!(black, 0, 0, 0); 212 | define_color!(white, 0xFF, 0xFF, 0xFF); 213 | define_color!(red, 0xFF, 0, 0); 214 | define_color!(green, 0, 0xFF, 0); 215 | define_color!(blue, 0, 0, 0xFF); 216 | } 217 | -------------------------------------------------------------------------------- /src/config/camera.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | #[derive(Deserialize, Debug)] 4 | pub struct Camera { 5 | pub fov: f32, 6 | pub width: u32, 7 | pub height: u32, 8 | pub position: [f32; 3], 9 | pub look_at: [f32; 3], 10 | pub up: [f32; 3], 11 | } 12 | 13 | impl Camera { 14 | pub fn new( 15 | fov: f32, 16 | width: u32, 17 | height: u32, 18 | position: [f32; 3], 19 | look_at: [f32; 3], 20 | up: [f32; 3], 21 | ) -> Camera { 22 | Camera { 23 | fov, 24 | width, 25 | height, 26 | position, 27 | look_at, 28 | up, 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/config/light.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | use crate::light::Falloff; 4 | 5 | #[derive(Deserialize, Debug)] 6 | #[serde(tag = "type")] 7 | pub enum Light { 8 | PointLight { 9 | origin: [f32; 3], 10 | color: [f32; 3], 11 | intensity: f32, 12 | falloff: Option, 13 | specular: Option, 14 | diffuse: Option, 15 | }, 16 | DirectionalLight { 17 | direction: [f32; 3], 18 | color: [f32; 3], 19 | intensity: f32, 20 | specular: Option, 21 | diffuse: Option, 22 | }, 23 | } 24 | -------------------------------------------------------------------------------- /src/config/material.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | use crate::material::IllumninationModel; 4 | 5 | #[derive(Deserialize, Debug, Clone)] 6 | pub enum Texture { 7 | #[serde(rename = "name")] 8 | Named(String), 9 | } 10 | 11 | pub type OptionalTexture = Option; 12 | 13 | #[derive(Deserialize, Debug, Clone)] 14 | pub struct Material { 15 | pub name: String, 16 | pub ambient_color: [f32; 3], 17 | pub ambient_texture: OptionalTexture, 18 | pub diffuse_color: [f32; 3], 19 | pub diffuse_texture: OptionalTexture, 20 | pub specular_color: [f32; 3], 21 | pub specular_texture: OptionalTexture, 22 | pub specular_exponent: f32, 23 | pub illumination_model: IllumninationModel, 24 | pub reflection_coefficient: Option, 25 | pub refraction_coefficient: Option, 26 | } 27 | -------------------------------------------------------------------------------- /src/config/mod.rs: -------------------------------------------------------------------------------- 1 | mod camera; 2 | mod light; 3 | mod material; 4 | mod object; 5 | mod scene; 6 | mod transform; 7 | 8 | use serde::Deserialize; 9 | 10 | pub use self::camera::Camera; 11 | pub use self::light::Light; 12 | pub use self::material::Material; 13 | pub use self::material::Texture; 14 | pub use self::object::Object; 15 | pub use self::scene::Scene; 16 | pub use self::transform::Transform; 17 | 18 | use std::error::Error; 19 | use std::fmt; 20 | use std::fs::File; 21 | use std::io; 22 | use std::io::Read; 23 | 24 | use crate::renderer::SuperSampling; 25 | 26 | #[derive(Debug)] 27 | pub struct ConfigError { 28 | cause: Option>, 29 | message: String, 30 | } 31 | 32 | impl ConfigError { 33 | fn new(message: String, cause: Option>) -> Self { 34 | ConfigError { message, cause } 35 | } 36 | } 37 | 38 | impl fmt::Display for ConfigError { 39 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 40 | match self.cause { 41 | Some(ref inner_error) => write!(f, "{} with error: {}", self.message, inner_error), 42 | None => write!(f, "{}", self.message), 43 | } 44 | } 45 | } 46 | 47 | impl Error for ConfigError { 48 | fn source(&self) -> Option<&(dyn Error + 'static)> { 49 | match self.cause { 50 | Some(ref error) => Some(error.as_ref()), 51 | None => None, 52 | } 53 | } 54 | } 55 | 56 | impl From for ConfigError { 57 | fn from(cause: io::Error) -> Self { 58 | Self::new( 59 | String::from("Failed to load/read config"), 60 | Some(Box::new(cause)), 61 | ) 62 | } 63 | } 64 | 65 | impl From for ConfigError { 66 | fn from(cause: serde_json::error::Error) -> Self { 67 | Self::new( 68 | String::from("Failed to parse config"), 69 | Some(Box::new(cause)), 70 | ) 71 | } 72 | } 73 | 74 | #[derive(Deserialize, Debug)] 75 | pub struct Config { 76 | pub max_depth: u32, 77 | pub super_sampling: SuperSampling, 78 | pub cameras: Vec, 79 | pub scenes: Vec, 80 | pub materials: Vec, 81 | } 82 | 83 | impl Config { 84 | pub fn new_from_file(path: &str) -> Result { 85 | let mut contents = String::new(); 86 | let mut file = File::open(path)?; 87 | 88 | file.read_to_string(&mut contents)?; 89 | let config = serde_json::from_str::(&contents)?; 90 | 91 | if config.scenes.is_empty() { 92 | return Err(ConfigError::new( 93 | String::from("Config should provide at least one scene, found none"), 94 | None, 95 | )); 96 | } 97 | 98 | if config.cameras.is_empty() { 99 | return Err(ConfigError::new( 100 | String::from("Config should provide at least one camera, found none"), 101 | None, 102 | )); 103 | } 104 | 105 | Ok(config) 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/config/object.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | use super::Transform; 4 | 5 | #[derive(Deserialize, Debug)] 6 | #[serde(tag = "type")] 7 | pub enum Object { 8 | Sphere { 9 | radius: f32, 10 | transforms: Option>, 11 | material_name: Option, 12 | }, 13 | Plane { 14 | normal: [f32; 3], 15 | transforms: Option>, 16 | material_name: Option, 17 | }, 18 | Mesh { 19 | path: String, 20 | transforms: Option>, 21 | material_name: Option, 22 | }, 23 | MeshInstance { 24 | path: String, 25 | transforms: Option>, 26 | material_name: Option, 27 | }, 28 | } 29 | -------------------------------------------------------------------------------- /src/config/scene.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | use super::light::Light; 4 | use super::object::Object; 5 | 6 | #[derive(Deserialize, Debug)] 7 | pub struct Scene { 8 | pub clear_color: [f32; 3], 9 | pub ambient_color: [f32; 3], 10 | pub objects: Vec, 11 | pub lights: Vec, 12 | } 13 | -------------------------------------------------------------------------------- /src/config/transform.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | use crate::geometry::Transformable; 4 | use crate::math; 5 | 6 | #[derive(Deserialize, Debug)] 7 | #[serde(tag = "type")] 8 | pub enum Transform { 9 | Translate { value: [f32; 3] }, 10 | Scale { value: [f32; 3] }, 11 | RotateX { value: f32 }, 12 | RotateY { value: f32 }, 13 | RotateZ { value: f32 }, 14 | } 15 | 16 | impl Transform { 17 | pub fn to_transform(&self) -> math::Transform { 18 | match *self { 19 | Transform::Translate { value } => { 20 | math::Transform::new(math::Matrix4::translate(value[0], value[1], value[2])) 21 | } 22 | Transform::Scale { value } => { 23 | math::Transform::new(math::Matrix4::scale(value[0], value[1], value[2])) 24 | } 25 | Transform::RotateX { value } => math::Transform::new(math::Matrix4::rot_x(value)), 26 | Transform::RotateY { value } => math::Transform::new(math::Matrix4::rot_y(value)), 27 | Transform::RotateZ { value } => math::Transform::new(math::Matrix4::rot_z(value)), 28 | } 29 | } 30 | 31 | pub fn perform(&self, transformable: &mut dyn Transformable) { 32 | let transform = self.to_transform(); 33 | 34 | transformable.transform(&transform); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/config_loader.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::path::Path; 3 | use std::rc::Rc; 4 | 5 | use crate::camera; 6 | use crate::color::Color; 7 | use crate::config; 8 | use crate::material; 9 | use crate::mesh_loader::MeshLoader; 10 | use crate::renderer; 11 | use crate::scene; 12 | use crate::texture; 13 | 14 | pub struct ConfigLoader { 15 | fallback_material: Rc, 16 | named_textures: HashMap>, 17 | } 18 | 19 | impl ConfigLoader { 20 | pub fn new(fallback_material: Rc) -> Self { 21 | Self { 22 | fallback_material, 23 | named_textures: HashMap::default(), 24 | } 25 | } 26 | 27 | pub fn register_named_texture(&mut self, name: &str, texture: Rc) { 28 | self.named_textures.insert(name.to_string(), texture); 29 | } 30 | 31 | pub fn load_renderer_from_config( 32 | &self, 33 | path: &str, 34 | ) -> Result<(renderer::Renderer, config::Config), Box> { 35 | let parsed_config = config::Config::new_from_file(path)?; 36 | let scene_path = Path::new(path).parent().unwrap(); 37 | let mut mesh_loader = MeshLoader::new(scene_path.to_path_buf()); 38 | 39 | let materials = parsed_config 40 | .materials 41 | .iter() 42 | .map(|material_config| { 43 | // TODO: Error handling 44 | ( 45 | material_config.name.to_owned(), 46 | Rc::new(material::Material::new_with_textures( 47 | Color::from(material_config.ambient_color), 48 | self.resolve_texture(&material_config.ambient_texture) 49 | .unwrap(), 50 | Color::from(material_config.diffuse_color), 51 | self.resolve_texture(&material_config.diffuse_texture) 52 | .unwrap(), 53 | Color::from(material_config.specular_color), 54 | self.resolve_texture(&material_config.specular_texture) 55 | .unwrap(), 56 | material_config.specular_exponent, 57 | material_config.illumination_model, 58 | material_config.reflection_coefficient, 59 | material_config.refraction_coefficient, 60 | )), 61 | ) 62 | }) 63 | .collect(); 64 | 65 | let scene = scene::Scene::new_from_config( 66 | parsed_config.scenes.first().unwrap(), 67 | &materials, 68 | &mut mesh_loader, 69 | Rc::clone(&self.fallback_material), 70 | )?; 71 | let camera_config = parsed_config 72 | .cameras 73 | .first() 74 | .expect("Config should contain at least one valid camera"); 75 | let camera = camera::Camera::from(camera_config); 76 | let renderer = renderer::Renderer::new(scene, camera, parsed_config.super_sampling); 77 | 78 | Ok((renderer, parsed_config)) 79 | } 80 | 81 | fn resolve_texture( 82 | &self, 83 | texture: &Option, 84 | ) -> Result> { 85 | match texture { 86 | None => Ok(None), 87 | Some(texture) => match texture { 88 | config::Texture::Named(name) => { 89 | let tex = self.named_textures.get(name).unwrap(); 90 | 91 | Ok(Some(Rc::clone(tex))) 92 | } 93 | }, 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/geometry/aabb.rs: -------------------------------------------------------------------------------- 1 | use super::{BoundingVolume, Triangle}; 2 | use crate::math::{Point3, Vector3}; 3 | use crate::ray::Ray; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct AABB { 7 | bounds: [Point3; 2], 8 | } 9 | 10 | impl Default for AABB { 11 | fn default() -> Self { 12 | Self::empty() 13 | } 14 | } 15 | 16 | impl AABB { 17 | pub fn new(min: Point3, max: Point3) -> Self { 18 | assert!(min.x <= max.x); 19 | assert!(min.y <= max.y); 20 | assert!(min.z <= max.z); 21 | 22 | Self { bounds: [min, max] } 23 | } 24 | 25 | pub fn from_triangle(triangle: &Triangle) -> Self { 26 | let mut min = Point3::new(f32::INFINITY, f32::INFINITY, f32::INFINITY); 27 | let mut max = Point3::new(f32::NEG_INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY); 28 | 29 | for vertex in &triangle.vertices { 30 | // Max 31 | max.x = f32::max(vertex.x, max.x); 32 | max.y = f32::max(vertex.y, max.y); 33 | max.z = f32::max(vertex.z, max.z); 34 | 35 | // Min 36 | min.x = f32::min(vertex.x, min.x); 37 | min.y = f32::min(vertex.y, min.y); 38 | min.z = f32::min(vertex.z, min.z); 39 | } 40 | 41 | Self::new(min, max) 42 | } 43 | 44 | pub fn empty() -> Self { 45 | Self { 46 | bounds: [ 47 | Point3::new(f32::NEG_INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY), 48 | Point3::new(f32::INFINITY, f32::INFINITY, f32::INFINITY), 49 | ], 50 | } 51 | } 52 | 53 | pub fn dimensions(&self) -> Vector3 { 54 | self.max() - self.min() 55 | } 56 | 57 | pub fn min(&self) -> Point3 { 58 | self.bounds[0] 59 | } 60 | 61 | pub fn max(&self) -> Point3 { 62 | self.bounds[1] 63 | } 64 | 65 | pub fn half(&self) -> Point3 { 66 | ((self.max() - self.min()) * 0.5).as_point() 67 | } 68 | 69 | pub fn center(&self) -> Point3 { 70 | (self.min() + self.half().as_vector()).as_point() 71 | } 72 | 73 | pub fn intersects_triangle_aabb(&self, triangle: &Triangle) -> bool { 74 | let bounding_box = Self::from_triangle(triangle); 75 | 76 | let min = self.min(); 77 | let max = self.max(); 78 | 79 | let b_min = bounding_box.min(); 80 | let b_max = bounding_box.max(); 81 | 82 | min.x <= b_max.x 83 | && max.x >= b_min.x 84 | && min.y <= b_max.y 85 | && max.y >= b_min.y 86 | && min.z <= b_max.z 87 | && max.z >= b_min.z 88 | } 89 | } 90 | 91 | impl BoundingVolume for AABB { 92 | fn from_triangles(triangles: &mut dyn Iterator) -> Self { 93 | let mut min = Point3::new(f32::INFINITY, f32::INFINITY, f32::INFINITY); 94 | let mut max = Point3::new(f32::NEG_INFINITY, f32::NEG_INFINITY, f32::NEG_INFINITY); 95 | 96 | for triangle in triangles { 97 | for vertex in &triangle.vertices { 98 | // Max 99 | max.x = f32::max(vertex.x, max.x); 100 | max.y = f32::max(vertex.y, max.y); 101 | max.z = f32::max(vertex.z, max.z); 102 | 103 | // Min 104 | min.x = f32::min(vertex.x, min.x); 105 | min.y = f32::min(vertex.y, min.y); 106 | min.z = f32::min(vertex.z, min.z); 107 | } 108 | } 109 | 110 | Self::new(min, max) 111 | } 112 | 113 | fn intersect(&self, ray: Ray) -> bool { 114 | let mut tmin = (self.bounds[ray.sign[0]].x - ray.origin.x) * ray.inv_direction.x; 115 | let mut tmax = (self.bounds[1 - ray.sign[0]].x - ray.origin.x) * ray.inv_direction.x; 116 | 117 | let tymin = (self.bounds[ray.sign[1]].y - ray.origin.y) * ray.inv_direction.y; 118 | let tymax = (self.bounds[1 - ray.sign[1]].y - ray.origin.y) * ray.inv_direction.y; 119 | 120 | if (tmin > tymax) || (tymin > tmax) { 121 | return false; 122 | } 123 | 124 | tmin = f32::max(tymin, tmin); 125 | tmax = f32::min(tymax, tmax); 126 | 127 | let tzmin = (self.bounds[ray.sign[2]].z - ray.origin.z) * ray.inv_direction.z; 128 | let tzmax = (self.bounds[1 - ray.sign[2]].z - ray.origin.z) * ray.inv_direction.z; 129 | 130 | if (tmin > tzmax) || (tzmin > tmax) { 131 | return false; 132 | } 133 | 134 | tmin = f32::max(tzmin, tmin); 135 | tmax = f32::min(tzmax, tmax); 136 | 137 | let mut t = tmin; 138 | 139 | if t <= 0.0 { 140 | t = tmax; 141 | 142 | if t <= 0.0 { 143 | return false; 144 | } 145 | } 146 | 147 | true 148 | } 149 | } 150 | 151 | #[cfg(test)] 152 | mod tests { 153 | use std::rc::Rc; 154 | 155 | use super::AABB; 156 | use crate::color::Color; 157 | use crate::geometry::triangle::{Normal, Triangle}; 158 | use crate::material::{IllumninationModel, Material}; 159 | use crate::math::{Point3, Vector3}; 160 | 161 | fn make_material() -> Material { 162 | Material::new( 163 | Color::black(), 164 | Color::black(), 165 | Color::black(), 166 | 0.0, 167 | IllumninationModel::Constant, 168 | None, 169 | None, 170 | ) 171 | } 172 | 173 | #[test] 174 | fn test_intersects_triangle_aabb_vertex_inside() { 175 | let aabb = AABB::new(Point3::new(-2.0, -2.0, -2.0), Point3::new(2.0, 2.0, 2.0)); 176 | 177 | let triangle = Triangle::new( 178 | Point3::at_origin(), 179 | Point3::new(3.0, 0.0, 0.0), 180 | Point3::new(0.0, 0.0, 3.0), 181 | Normal::Face(Vector3::new(0.0, 1.0, 0.0)), 182 | None, 183 | Rc::new(make_material()), 184 | ); 185 | 186 | assert!(aabb.intersects_triangle_aabb(&triangle)); 187 | } 188 | 189 | #[test] 190 | fn test_intersects_triangle_aabb_edge() { 191 | let aabb = AABB::new(Point3::new(-2.0, -2.0, -2.0), Point3::new(2.0, 2.0, 2.0)); 192 | 193 | let triangle = Triangle::new( 194 | Point3::new(3.0, 0.0, 0.0), 195 | Point3::new(0.0, 0.0, 3.0), 196 | Point3::new(3.0, 0.0, 3.0), 197 | Normal::Face(Vector3::new(0.0, 1.0, 0.0)), 198 | None, 199 | Rc::new(make_material()), 200 | ); 201 | 202 | assert!(aabb.intersects_triangle_aabb(&triangle)); 203 | } 204 | 205 | #[test] 206 | fn test_intersects_triangle_aabb_realistic_trivial() { 207 | // This case is entirely trivial: The triangle beeing tested is tested 208 | // against the bounding box of the whole mesh. 209 | 210 | let aabb = AABB::new( 211 | Point3::new(-0.23978, -0.282958, -0.472247), 212 | Point3::new(0.207395, 0.422022, 0.527753), 213 | ); 214 | 215 | let triangle = Triangle::new( 216 | Point3::new(-0.0148929, -0.270744, 0.213293), 217 | Point3::new(-0.0132528, -0.270767, 0.213397), 218 | Point3::new(-0.0146446, -0.270253, 0.214432), 219 | Normal::Vertex( 220 | Vector3::new(-0.0331532, -0.915051, 0.401972), 221 | Vector3::new(-0.056424, -0.938723, 0.340022), 222 | Vector3::new(-0.114637, -0.897883, 0.425047), 223 | ), 224 | None, 225 | Rc::new(make_material()), 226 | ); 227 | 228 | assert!(aabb.intersects_triangle_aabb(&triangle)); 229 | } 230 | 231 | #[test] 232 | fn test_intersects_triangle_aabb_realistic_1() { 233 | let aabb = AABB::new( 234 | Point3::new(-0.23978, -0.282958, -0.472247), 235 | Point3::new(0.207395, 0.422022, 0.527753), 236 | ); 237 | 238 | let triangle = Triangle::new( 239 | Point3::new(-0.219407, -0.248815, -0.229673), 240 | Point3::new(-0.218334, -0.252232, -0.224549), 241 | Point3::new(-0.219816, -0.248815, -0.219424), 242 | Normal::Vertex( 243 | Vector3::new(-0.93934, -0.338087, -0.0577654), 244 | Vector3::new(-0.935684, -0.351144, -0.0345402), 245 | Vector3::new(-0.943101, -0.331952, -0.019191), 246 | ), 247 | None, 248 | Rc::new(make_material()), 249 | ); 250 | 251 | assert!(aabb.intersects_triangle_aabb(&triangle)); 252 | } 253 | 254 | #[test] 255 | fn test_intersects_triangle_aabb_realistic_2() { 256 | let aabb = AABB::new( 257 | Point3::new(-0.23978, -0.282958, -0.472247), 258 | Point3::new(0.207395, 0.422022, 0.527753), 259 | ); 260 | 261 | let triangle = Triangle::new( 262 | Point3::new(0.136228, 0.217532, -0.149386), 263 | Point3::new(0.135274, 0.217532, -0.150971), 264 | Point3::new(0.136983, 0.21924, -0.149388), 265 | Normal::Vertex( 266 | Vector3::new(0.85279, -0.201127, -0.481972), 267 | Vector3::new(0.820284, -0.230435, -0.523482), 268 | Vector3::new(0.732158, -0.430477, -0.527859), 269 | ), 270 | None, 271 | Rc::new(make_material()), 272 | ); 273 | 274 | assert!(aabb.intersects_triangle_aabb(&triangle)); 275 | } 276 | } 277 | -------------------------------------------------------------------------------- /src/geometry/extent_volume.rs: -------------------------------------------------------------------------------- 1 | use std::mem; 2 | 3 | use super::{BoundingVolume, Ray, Triangle}; 4 | use crate::math::Vector3; 5 | 6 | // This approach is based on Ray Tracing Complex Scenes, Kay and Kajiya, 1986[0]. 7 | // 8 | // 0: https://dl.acm.org/doi/pdf/10.1145/15886.15916 9 | 10 | const NUM_PLANE_SET_NORMALS: usize = 7; 11 | const PLAN_SET_NORMALS: &[Vector3; NUM_PLANE_SET_NORMALS] = &[ 12 | Vector3::new(1.0, 0.0, 0.0), 13 | Vector3::new(0.0, 1.0, 0.0), 14 | Vector3::new(0.0, 0.0, 1.0), 15 | // 0.577350269 = 3.0_f32.sqrt() / 3.0 but alas `sqrt` isn't a const fn 16 | // so it cannot be used in this context. 17 | Vector3::new(0.577_350_26, 0.577_350_26, 0.577_350_26), 18 | Vector3::new(-0.577_350_26, 0.577_350_26, 0.577_350_26), 19 | Vector3::new(-0.577_350_26, -0.577_350_26, 0.577_350_26), 20 | Vector3::new(0.577_350_26, -0.577_350_26, 0.577_350_26), 21 | ]; 22 | 23 | pub struct ExtentVolume { 24 | distances: [[f32; 2]; NUM_PLANE_SET_NORMALS], 25 | } 26 | 27 | impl BoundingVolume for ExtentVolume { 28 | fn from_triangles(triangles: &mut dyn Iterator) -> Self { 29 | let mut distances = [[std::f32::INFINITY, std::f32::NEG_INFINITY]; NUM_PLANE_SET_NORMALS]; 30 | 31 | for triangle in triangles { 32 | for point in &triangle.vertices { 33 | let vertex = point.as_vector(); 34 | 35 | for (normal_idx, plane_normal) in PLAN_SET_NORMALS.iter().enumerate() { 36 | let distance = plane_normal.dot(&vertex); 37 | 38 | distances[normal_idx][0] = f32::min(distances[normal_idx][0], distance); 39 | distances[normal_idx][1] = f32::max(distances[normal_idx][1], distance); 40 | } 41 | } 42 | } 43 | 44 | Self { distances } 45 | } 46 | 47 | fn intersect(&self, ray: Ray) -> bool { 48 | let (precomputed_numerator, precomputed_denominator) = Self::precompute(&ray); 49 | 50 | let mut t_near = std::f32::NEG_INFINITY; 51 | let mut t_far = std::f32::INFINITY; 52 | 53 | for i in 0..NUM_PLANE_SET_NORMALS { 54 | let mut tn = 55 | (self.distances[i][0] - precomputed_numerator[i]) / precomputed_denominator[i]; 56 | let mut tf = 57 | (self.distances[i][1] - precomputed_numerator[i]) / precomputed_denominator[i]; 58 | 59 | if precomputed_denominator[i] < 0.0 { 60 | mem::swap(&mut tn, &mut tf); 61 | } 62 | 63 | t_near = f32::max(tn, t_near); 64 | t_far = f32::min(tf, t_far); 65 | 66 | if t_near > t_far { 67 | return false; 68 | } 69 | } 70 | 71 | true 72 | } 73 | } 74 | 75 | impl ExtentVolume { 76 | fn precompute(ray: &Ray) -> ([f32; NUM_PLANE_SET_NORMALS], [f32; NUM_PLANE_SET_NORMALS]) { 77 | let origin_vector = ray.origin.as_vector(); 78 | 79 | let mut precomputed_numerator: [f32; NUM_PLANE_SET_NORMALS] = 80 | [std::f32::NAN; NUM_PLANE_SET_NORMALS]; 81 | let mut precomputed_denominator: [f32; NUM_PLANE_SET_NORMALS] = 82 | [std::f32::NAN; NUM_PLANE_SET_NORMALS]; 83 | 84 | for i in 0..NUM_PLANE_SET_NORMALS { 85 | precomputed_numerator[i] = PLAN_SET_NORMALS[i].dot(&origin_vector); 86 | precomputed_denominator[i] = PLAN_SET_NORMALS[i].dot(&ray.direction); 87 | } 88 | 89 | (precomputed_numerator, precomputed_denominator) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/geometry/instance.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use crate::geometry::mesh::Mesh; 4 | use crate::geometry::{BoundingVolume, Intersectable, Shape, Transformable, TriangleStorage}; 5 | use crate::intersection::Intersection; 6 | use crate::material::Material; 7 | use crate::math::{Matrix4, Transform}; 8 | use crate::ray::Ray; 9 | 10 | pub struct Instance { 11 | mesh: Rc>, 12 | model_matrix: Matrix4, 13 | inverse_model_matrix: Matrix4, 14 | material: Rc, 15 | } 16 | 17 | impl<'a, V: BoundingVolume, S: TriangleStorage<'a>> Instance { 18 | pub fn new(mesh: Rc>, material: Rc) -> Self { 19 | Self { 20 | mesh, 21 | model_matrix: Matrix4::identity(), 22 | inverse_model_matrix: Matrix4::identity(), 23 | material, 24 | } 25 | } 26 | } 27 | 28 | impl TriangleStorage<'a>> Transformable for Instance { 29 | fn transform(&mut self, transform: &Transform) { 30 | self.model_matrix = self.model_matrix * transform.matrix; 31 | } 32 | 33 | fn apply_transforms(&mut self, transforms: &[Transform]) { 34 | let mut world_to_view = self.model_matrix; 35 | 36 | for transform in transforms { 37 | world_to_view = world_to_view * transform.matrix; 38 | } 39 | 40 | self.model_matrix = world_to_view; 41 | self.inverse_model_matrix = world_to_view.inverse().unwrap(); 42 | } 43 | } 44 | 45 | impl TriangleStorage<'a>> Intersectable for Instance { 46 | fn intersect(&self, ray: Ray, cull: bool) -> Option { 47 | let inverse_model_matrix = self.inverse_model_matrix; 48 | let new_ray = Ray::new( 49 | inverse_model_matrix * ray.origin, 50 | (inverse_model_matrix * ray.direction).normalize(), 51 | Some(ray.medium_refraction), 52 | ); 53 | 54 | self.mesh.as_ref().intersect(new_ray, cull).map(|mut i| { 55 | i.normal = (inverse_model_matrix.transpose() * i.normal).normalize(); 56 | i.point = self.model_matrix * i.point; 57 | i.t = (ray.origin - i.point).length().abs(); 58 | i.shape = self; 59 | 60 | i 61 | }) 62 | } 63 | } 64 | 65 | impl TriangleStorage<'a>> Shape for Instance { 66 | fn material(&self) -> &Material { 67 | &self.material 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/geometry/mesh.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use super::triangle::Normal; 4 | use super::{BoundingVolume, Intersectable, Material, Transformable, Triangle, TriangleStorage}; 5 | use crate::intersection::Intersection; 6 | use crate::math::{Point3, Transform}; 7 | use crate::ray::Ray; 8 | 9 | #[derive(Debug)] 10 | pub struct Mesh { 11 | storage: S, 12 | bounding_volume: V, 13 | } 14 | 15 | impl<'a, V: BoundingVolume, S: TriangleStorage<'a>> Mesh { 16 | pub fn new(triangles: Vec) -> Self { 17 | let bounding_volume = V::from_triangles(&mut triangles.iter()); 18 | let storage = S::new(triangles); 19 | 20 | Self { 21 | storage, 22 | bounding_volume, 23 | } 24 | } 25 | 26 | pub fn rebuild_accelleration_structure(&mut self) { 27 | self.storage.build(); 28 | } 29 | 30 | pub fn cube(material: Rc) -> Self { 31 | let vertices = [ 32 | Point3::new(-1.0, -1.0, 1.0), 33 | Point3::new(1.0, -1.0, 1.0), 34 | Point3::new(1.0, 1.0, 1.0), 35 | Point3::new(-1.0, 1.0, 1.0), 36 | Point3::new(-1.0, -1.0, -1.0), 37 | Point3::new(1.0, -1.0, -1.0), 38 | Point3::new(1.0, 1.0, -1.0), 39 | Point3::new(-1.0, 1.0, -1.0), 40 | ]; 41 | 42 | let triangles = Self::from_triangles( 43 | &[ 44 | vertices[0], 45 | vertices[1], 46 | vertices[2], 47 | vertices[2], 48 | vertices[3], 49 | vertices[0], 50 | vertices[1], 51 | vertices[5], 52 | vertices[6], 53 | vertices[6], 54 | vertices[2], 55 | vertices[1], 56 | vertices[7], 57 | vertices[6], 58 | vertices[5], 59 | vertices[5], 60 | vertices[4], 61 | vertices[7], 62 | vertices[4], 63 | vertices[0], 64 | vertices[3], 65 | vertices[3], 66 | vertices[7], 67 | vertices[4], 68 | vertices[4], 69 | vertices[5], 70 | vertices[1], 71 | vertices[1], 72 | vertices[0], 73 | vertices[4], 74 | vertices[3], 75 | vertices[2], 76 | vertices[6], 77 | vertices[6], 78 | vertices[7], 79 | vertices[3], 80 | ], 81 | material, 82 | ); 83 | 84 | Self::new(triangles) 85 | } 86 | 87 | fn from_triangles(vertices: &[Point3], material: Rc) -> Vec { 88 | assert!( 89 | vertices.len() % 3 == 0, 90 | "Number of vertices should be a multiple of 3" 91 | ); 92 | (0..vertices.len() / 3) 93 | .map(|i| { 94 | let a = vertices[i * 3]; 95 | let b = vertices[i * 3 + 1]; 96 | let c = vertices[i * 3 + 2]; 97 | let ab = a - b; 98 | let ac = a - c; 99 | let normal = ab.cross(&ac).normalize(); 100 | 101 | Triangle::new( 102 | vertices[i * 3], 103 | vertices[i * 3 + 1], 104 | vertices[i * 3 + 2], 105 | Normal::Face(normal), 106 | None, 107 | Rc::clone(&material), 108 | ) 109 | }) 110 | .collect() 111 | } 112 | } 113 | 114 | impl TriangleStorage<'a>> Transformable for Mesh { 115 | fn transform(&mut self, transform: &Transform) { 116 | self.storage.transform(transform); 117 | 118 | self.bounding_volume = V::from_triangles(&mut self.storage.all()); 119 | } 120 | 121 | fn apply_transforms(&mut self, transforms: &[Transform]) { 122 | self.storage.apply_transforms(transforms); 123 | self.bounding_volume = V::from_triangles(&mut self.storage.all()); 124 | } 125 | } 126 | 127 | impl TriangleStorage<'a>> Intersectable for Mesh { 128 | fn intersect(&self, ray: Ray, cull: bool) -> Option { 129 | if !self.bounding_volume.intersect(ray) { 130 | return None; 131 | } 132 | 133 | let mut nearest_intersection: Option = None; 134 | 135 | for triangle in self.storage.intersect(ray, cull) { 136 | let Some(intersection) = triangle.intersect(ray, cull) else { 137 | continue; 138 | }; 139 | 140 | let nearest = nearest_intersection.get_or_insert(intersection); 141 | if intersection.t < nearest.t { 142 | *nearest = intersection; 143 | } 144 | } 145 | 146 | nearest_intersection 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /src/geometry/mod.rs: -------------------------------------------------------------------------------- 1 | mod aabb; 2 | mod extent_volume; 3 | mod instance; 4 | mod mesh; 5 | mod octtree; 6 | mod plane; 7 | mod simple_triangle_storage; 8 | mod sphere; 9 | pub mod triangle; 10 | 11 | pub use self::aabb::AABB; 12 | pub use self::extent_volume::ExtentVolume; 13 | pub use self::instance::Instance; 14 | pub use self::mesh::Mesh; 15 | pub use self::plane::Plane; 16 | pub use self::sphere::Sphere; 17 | pub use self::triangle::Triangle; 18 | 19 | pub use self::octtree::Octree; 20 | pub use self::simple_triangle_storage::SimpleTriangleStorage; 21 | 22 | use crate::intersection::Intersection; 23 | use crate::material::Material; 24 | use crate::math::Transform; 25 | use crate::ray::Ray; 26 | 27 | pub trait Intersectable { 28 | fn intersect(&self, ray: Ray, cull: bool) -> Option; 29 | } 30 | 31 | pub trait Transformable { 32 | fn transform(&mut self, transform: &Transform); 33 | fn apply_transforms(&mut self, transforms: &[Transform]) { 34 | for transform in transforms { 35 | self.transform(transform); 36 | } 37 | } 38 | } 39 | 40 | pub trait Shape: Intersectable { 41 | fn material(&self) -> &Material; 42 | } 43 | 44 | pub trait BoundingVolume { 45 | fn from_triangles(triangles: &mut dyn Iterator) -> Self; 46 | fn intersect(&self, ray: Ray) -> bool; 47 | } 48 | 49 | pub trait TriangleStorage<'a>: Transformable { 50 | type Iterator: Iterator; 51 | type IteratorMut: Iterator; 52 | type IntersectionIterator: Iterator; 53 | 54 | fn new(triangles: Vec) -> Self; 55 | fn build(&mut self); 56 | fn intersect(&'a self, ray: Ray, cull: bool) -> Self::IntersectionIterator; 57 | fn all(&'a self) -> Self::Iterator; 58 | fn all_mut(&'a mut self) -> Self::IteratorMut; 59 | } 60 | -------------------------------------------------------------------------------- /src/geometry/octtree.rs: -------------------------------------------------------------------------------- 1 | use std::collections::{HashSet, VecDeque}; 2 | use std::ops::{Index, IndexMut}; 3 | 4 | use tracing::debug; 5 | 6 | use super::{BoundingVolume, Transformable, Triangle, TriangleStorage, AABB}; 7 | use crate::math::Point3; 8 | use crate::math::Transform; 9 | use crate::ray::Ray; 10 | 11 | #[derive(Debug, Clone, Copy)] 12 | struct NodeId(usize); 13 | 14 | impl NodeId { 15 | const fn none() -> Self { 16 | NodeId(usize::MAX) 17 | } 18 | } 19 | 20 | impl From for NodeId { 21 | fn from(id: usize) -> Self { 22 | Self(id) 23 | } 24 | } 25 | 26 | #[derive(Debug)] 27 | struct Node { 28 | metadata: M, 29 | data: T, 30 | } 31 | 32 | impl Node { 33 | fn new(data: T) -> Self { 34 | Self { 35 | data, 36 | metadata: M::default(), 37 | } 38 | } 39 | } 40 | 41 | #[derive(Debug)] 42 | struct Arena { 43 | nodes: Vec>, 44 | } 45 | 46 | impl Arena { 47 | fn with_capacity(capacity: usize) -> Self { 48 | Self { 49 | nodes: Vec::with_capacity(capacity), 50 | } 51 | } 52 | 53 | fn new_node(&mut self, data: T) -> NodeId { 54 | let next_index = self.nodes.len(); 55 | 56 | let node = Node::new(data); 57 | 58 | self.nodes.push(node); 59 | 60 | NodeId::from(next_index) 61 | } 62 | 63 | fn clear(&mut self) { 64 | self.nodes.clear(); 65 | } 66 | 67 | fn num_nodes(&self) -> usize { 68 | self.nodes.len() 69 | } 70 | } 71 | 72 | impl Index for Arena { 73 | type Output = Node; 74 | 75 | fn index(&self, id: NodeId) -> &Self::Output { 76 | &self.nodes[id.0] 77 | } 78 | } 79 | 80 | impl IndexMut for Arena { 81 | fn index_mut(&mut self, id: NodeId) -> &mut Self::Output { 82 | &mut self.nodes[id.0] 83 | } 84 | } 85 | 86 | #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] 87 | pub struct TriangleId(usize); 88 | 89 | impl TriangleId { 90 | fn value(&self) -> usize { 91 | self.0 92 | } 93 | } 94 | 95 | impl From for TriangleId { 96 | fn from(id: usize) -> Self { 97 | Self(id) 98 | } 99 | } 100 | 101 | pub struct IntersectionIterator<'a, I> { 102 | ids: I, 103 | triangles: &'a [Triangle], 104 | } 105 | 106 | impl<'a, I> IntersectionIterator<'a, I> { 107 | fn new(ids: I, triangles: &'a [Triangle]) -> Self { 108 | Self { ids, triangles } 109 | } 110 | } 111 | 112 | impl<'a, I> Iterator for IntersectionIterator<'a, I> 113 | where 114 | I: Iterator, 115 | { 116 | type Item = &'a Triangle; 117 | 118 | fn next(&mut self) -> Option { 119 | let item = self 120 | .ids 121 | .next() 122 | .and_then(|triangle_id| self.triangles.get(triangle_id.value())); 123 | 124 | item 125 | } 126 | } 127 | 128 | #[derive(Debug)] 129 | struct Metadata { 130 | children: [NodeId; 8], 131 | bounding_box: AABB, 132 | is_leaf: bool, 133 | is_empty: bool, 134 | } 135 | 136 | impl Default for Metadata { 137 | fn default() -> Self { 138 | Self { 139 | children: [NodeId::none(); 8], 140 | bounding_box: AABB::empty(), 141 | is_leaf: false, 142 | is_empty: false, 143 | } 144 | } 145 | } 146 | 147 | const MAX_DEPTH: usize = 10; 148 | const MIN_SIZE: f32 = 0.00001; 149 | const MIN_TRIANGLES_PER_NODE: usize = 10; 150 | 151 | #[derive(Debug)] 152 | pub struct Octree { 153 | triangles: Vec, 154 | arena: Arena>, 155 | root: NodeId, 156 | } 157 | 158 | impl Octree { 159 | fn visit_nodes(&self, mut callback: F) 160 | where 161 | F: FnMut(NodeId), 162 | { 163 | let mut to_visit = VecDeque::new(); 164 | to_visit.push_back(self.root); 165 | 166 | while let Some(node_id) = to_visit.pop_back() { 167 | callback(node_id); 168 | 169 | if !self.arena[node_id].metadata.is_leaf { 170 | for child_id in &self.arena[node_id].metadata.children { 171 | to_visit.push_back(*child_id); 172 | } 173 | } 174 | } 175 | } 176 | 177 | fn print_stats(&self) { 178 | debug!("Octree depth: {}", (self.arena.num_nodes() as f64).log(8.0)); 179 | debug!("Octree number of nodes: {}", self.arena.num_nodes()); 180 | 181 | let mut leaf_count = 0.0; 182 | let mut leaf_triangle_count = 0.0; 183 | self.visit_nodes(|node_id| { 184 | if self.arena[node_id].metadata.is_leaf { 185 | leaf_count += 1.0; 186 | leaf_triangle_count += self.arena[node_id].data.len() as f64; 187 | } 188 | }); 189 | 190 | debug!( 191 | "Octree average triangle count in leaf nodes: {}", 192 | leaf_triangle_count / leaf_count 193 | ); 194 | } 195 | 196 | fn rebuild(&mut self) { 197 | self.arena.clear(); 198 | 199 | let root_id = self 200 | .arena 201 | .new_node((0..self.triangles.len()).map(TriangleId::from).collect()); 202 | 203 | let mut iterator = self.triangles.iter(); 204 | self.root = root_id; 205 | self.arena[root_id].metadata.bounding_box = AABB::from_triangles(&mut iterator); 206 | self.build(root_id, 1); 207 | self.print_stats(); 208 | } 209 | 210 | fn build(&mut self, node_id: NodeId, depth: usize) { 211 | let (mut child_nodes, mut child_bounding_volumes) = { 212 | let node = &mut self.arena[node_id]; 213 | let bounding_box = &node.metadata.bounding_box; 214 | 215 | // Exit condition triangles per node 216 | if node.data.len() <= MIN_TRIANGLES_PER_NODE { 217 | node.metadata.is_leaf = true; 218 | node.metadata.is_empty = node.data.is_empty(); 219 | return; 220 | } 221 | 222 | let dimensions = bounding_box.dimensions(); 223 | 224 | // Exit condition smallest node 225 | if dimensions.x <= MIN_SIZE && dimensions.y <= MIN_SIZE && dimensions.z <= MIN_SIZE { 226 | node.metadata.is_leaf = true; 227 | return; 228 | } 229 | 230 | // Exit condition max depth 231 | if depth >= MAX_DEPTH { 232 | node.metadata.is_leaf = true; 233 | return; 234 | } 235 | 236 | let child_bounding_volumes = Self::build_octants(bounding_box); 237 | let mut to_delete: HashSet = HashSet::default(); 238 | let mut child_nodes: VecDeque> = VecDeque::with_capacity(8); 239 | for _ in 0..8 { 240 | child_nodes.push_back(HashSet::default()); 241 | } 242 | 243 | for triangle_id in &node.data { 244 | for i in 0..8 { 245 | let triangle = &self.triangles[triangle_id.value()]; 246 | 247 | if child_bounding_volumes[i].intersects_triangle_aabb(triangle) { 248 | to_delete.insert(*triangle_id); 249 | child_nodes[i].insert(*triangle_id); 250 | } 251 | } 252 | } 253 | 254 | node.data = node.data.difference(&to_delete).cloned().collect(); 255 | assert!(node.data.is_empty()); 256 | (child_nodes, child_bounding_volumes) 257 | }; 258 | 259 | for i in 0..8 { 260 | let id = self.arena.new_node(child_nodes.pop_front().unwrap()); 261 | self.arena[id].metadata.bounding_box = child_bounding_volumes.pop_front().unwrap(); 262 | 263 | self.arena[node_id].metadata.children[i] = id; 264 | } 265 | 266 | for i in 0..8 { 267 | self.build(self.arena[node_id].metadata.children[i], depth + 1); 268 | } 269 | } 270 | 271 | fn build_octants(bounding_box: &AABB) -> VecDeque { 272 | let center = bounding_box.center(); 273 | let (min, max) = (bounding_box.min(), bounding_box.max()); 274 | 275 | let mut child_bounding_volumes = VecDeque::with_capacity(8); 276 | // Left 277 | child_bounding_volumes.push_back(AABB::new(min, center)); 278 | child_bounding_volumes.push_back(AABB::new( 279 | Point3::new(min.x, min.y, center.z), 280 | Point3::new(center.x, center.y, max.z), 281 | )); 282 | child_bounding_volumes.push_back(AABB::new( 283 | Point3::new(min.x, center.y, min.z), 284 | Point3::new(center.x, max.y, center.z), 285 | )); 286 | child_bounding_volumes.push_back(AABB::new( 287 | Point3::new(min.x, center.y, center.z), 288 | Point3::new(center.x, max.y, max.z), 289 | )); 290 | // Right 291 | child_bounding_volumes.push_back(AABB::new(center, max)); 292 | child_bounding_volumes.push_back(AABB::new( 293 | Point3::new(center.x, center.y, min.z), 294 | Point3::new(max.x, max.y, center.z), 295 | )); 296 | child_bounding_volumes.push_back(AABB::new( 297 | Point3::new(center.x, min.y, center.z), 298 | Point3::new(max.x, center.y, max.z), 299 | )); 300 | child_bounding_volumes.push_back(AABB::new( 301 | Point3::new(center.x, min.y, min.z), 302 | Point3::new(max.x, center.y, center.z), 303 | )); 304 | 305 | child_bounding_volumes 306 | } 307 | } 308 | 309 | impl Transformable for Octree { 310 | fn transform(&mut self, transform: &Transform) { 311 | for triangle in self.all_mut() { 312 | triangle.transform(transform); 313 | } 314 | 315 | self.rebuild(); 316 | } 317 | 318 | fn apply_transforms(&mut self, transforms: &[Transform]) { 319 | for transform in transforms { 320 | for triangle in self.all_mut() { 321 | triangle.transform(transform); 322 | } 323 | } 324 | 325 | self.rebuild(); 326 | } 327 | } 328 | 329 | impl<'a> TriangleStorage<'a> for Octree { 330 | type Iterator = std::slice::Iter<'a, Triangle>; 331 | type IteratorMut = std::slice::IterMut<'a, Triangle>; 332 | type IntersectionIterator = 333 | IntersectionIterator<'a, std::collections::hash_set::IntoIter>; 334 | 335 | fn new(triangles: Vec) -> Self { 336 | let mut arena = Arena::with_capacity((triangles.len() as f64).log(8.0) as usize); 337 | // let mut arena = Arena::new(); 338 | let root_id = arena.new_node((0..triangles.len()).map(TriangleId::from).collect()); 339 | 340 | Self { 341 | triangles, 342 | arena, 343 | root: root_id, 344 | } 345 | } 346 | 347 | fn build(&mut self) { 348 | self.rebuild(); 349 | } 350 | 351 | fn intersect(&'a self, ray: Ray, _cull: bool) -> Self::IntersectionIterator { 352 | let mut node_ids: VecDeque = VecDeque::new(); 353 | node_ids.push_front(self.root); 354 | let mut triangles_to_test = HashSet::new(); 355 | 356 | while let Some(id) = node_ids.pop_back() { 357 | let child_node = &self.arena[id]; 358 | 359 | if child_node.metadata.bounding_box.intersect(ray) { 360 | if !child_node.metadata.is_leaf { 361 | for octant_id in child_node.metadata.children.iter().cloned() { 362 | node_ids.push_back(octant_id); 363 | } 364 | } 365 | 366 | for triangle_id in child_node.data.iter().cloned() { 367 | triangles_to_test.insert(triangle_id); 368 | } 369 | } 370 | } 371 | 372 | IntersectionIterator::new(triangles_to_test.into_iter(), &self.triangles) 373 | } 374 | 375 | fn all(&'a self) -> Self::Iterator { 376 | self.triangles.iter() 377 | } 378 | 379 | fn all_mut(&'a mut self) -> Self::IteratorMut { 380 | self.triangles.iter_mut() 381 | } 382 | } 383 | 384 | #[cfg(test)] 385 | mod tests { 386 | use super::*; 387 | 388 | #[test] 389 | fn test_build_octants() { 390 | let aabb = AABB::new(Point3::at_origin(), Point3::new(1.0, 1.0, 1.0)); 391 | let expected: [(Point3, Point3); 8] = [ 392 | (Point3::at_origin(), Point3::new(0.5, 0.5, 0.5)), 393 | (Point3::new(0.0, 0.0, 0.5), Point3::new(0.5, 0.5, 1.0)), 394 | (Point3::new(0.5, 0.0, 0.0), Point3::new(1.0, 0.5, 0.5)), 395 | (Point3::new(0.5, 0.0, 0.5), Point3::new(1.0, 0.5, 1.0)), 396 | (Point3::new(0.0, 0.5, 0.0), Point3::new(0.5, 1.0, 0.5)), 397 | (Point3::new(0.0, 0.5, 0.5), Point3::new(0.5, 1.0, 1.0)), 398 | (Point3::new(0.5, 0.5, 0.0), Point3::new(1.0, 1.0, 0.5)), 399 | (Point3::new(0.5, 0.5, 0.5), Point3::new(1.0, 1.0, 1.0)), 400 | ]; 401 | 402 | let result = Octree::build_octants(&aabb); 403 | 404 | for r in &result { 405 | let equal = expected 406 | .iter() 407 | .map(|(min, max)| r.min().fuzzy_equal(min) && r.max().fuzzy_equal(max)) 408 | .filter(|x| *x) 409 | .count() 410 | == 1; 411 | 412 | assert!(equal, "Unexpected bounding box {:?}", r); 413 | } 414 | } 415 | } 416 | -------------------------------------------------------------------------------- /src/geometry/plane.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use super::{Intersectable, Shape, Transformable}; 4 | use crate::intersection::Intersection; 5 | use crate::material::Material; 6 | use crate::math::EPSILON; 7 | use crate::math::{Point3, Transform, Vector3}; 8 | use crate::ray::Ray; 9 | 10 | pub struct Plane { 11 | pub origin: Point3, 12 | pub normal: Vector3, 13 | material: Rc, 14 | } 15 | 16 | impl Plane { 17 | pub fn new(origin: Point3, normal: Vector3, material: Rc) -> Plane { 18 | Plane { 19 | origin, 20 | normal: normal.normalize(), 21 | material, 22 | } 23 | } 24 | } 25 | 26 | impl Shape for Plane { 27 | fn material(&self) -> &Material { 28 | &self.material 29 | } 30 | } 31 | 32 | impl Intersectable for Plane { 33 | fn intersect(&self, ray: Ray, _: bool) -> Option { 34 | let denominator = self.normal.dot(&ray.direction); 35 | 36 | if denominator.abs() <= EPSILON { 37 | return None; 38 | } 39 | 40 | let t = ((self.origin - ray.origin).dot(&self.normal)) / denominator; 41 | 42 | if t >= 0.0 { 43 | let intersection_point = (ray.origin + ray.direction * t).as_point(); 44 | 45 | let intersection = 46 | Intersection::new(t, self, intersection_point, ray, self.normal, false, None); 47 | 48 | return Some(intersection); 49 | } 50 | 51 | None 52 | } 53 | } 54 | 55 | impl Transformable for Plane { 56 | fn transform(&mut self, transform: &Transform) { 57 | self.origin = transform.matrix * self.origin; 58 | self.normal = (transform.normal_matrix * self.normal).normalize(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/geometry/simple_triangle_storage.rs: -------------------------------------------------------------------------------- 1 | use super::{Transformable, Triangle, TriangleStorage}; 2 | use crate::math::Transform; 3 | use crate::ray::Ray; 4 | 5 | #[derive(Debug)] 6 | pub struct SimpleTriangleStorage { 7 | triangles: Vec, 8 | } 9 | 10 | impl Transformable for SimpleTriangleStorage { 11 | fn transform(&mut self, transform: &Transform) { 12 | for triangle in self.all_mut() { 13 | triangle.transform(transform); 14 | } 15 | } 16 | } 17 | 18 | impl<'a> TriangleStorage<'a> for SimpleTriangleStorage { 19 | type Iterator = std::slice::Iter<'a, Triangle>; 20 | type IteratorMut = std::slice::IterMut<'a, Triangle>; 21 | type IntersectionIterator = std::slice::Iter<'a, Triangle>; 22 | 23 | fn new(triangles: Vec) -> Self { 24 | Self { triangles } 25 | } 26 | 27 | fn build(&mut self) { 28 | // Not much to build when you have no acceleration structure 29 | } 30 | 31 | fn intersect(&'a self, _ray: Ray, _cull: bool) -> Self::Iterator { 32 | self.all() 33 | } 34 | 35 | fn all(&'a self) -> Self::Iterator { 36 | self.triangles.iter() 37 | } 38 | 39 | fn all_mut(&'a mut self) -> Self::IteratorMut { 40 | self.triangles.iter_mut() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/geometry/sphere.rs: -------------------------------------------------------------------------------- 1 | use std::f32::consts::PI; 2 | use std::rc::Rc; 3 | 4 | use crate::intersection::Intersection; 5 | use crate::material::Material; 6 | use crate::math::{Point3, Transform}; 7 | use crate::ray::Ray; 8 | use crate::texture::TextureCoord; 9 | 10 | use super::{Intersectable, Shape, Transformable}; 11 | 12 | #[derive(Debug)] 13 | pub struct Sphere { 14 | pub origin: Point3, 15 | pub radius: f32, 16 | material: Rc, 17 | } 18 | 19 | impl Sphere { 20 | pub fn new(origin: Point3, radius: f32, material: Rc) -> Self { 21 | Self { 22 | origin, 23 | radius, 24 | material, 25 | } 26 | } 27 | } 28 | 29 | impl Shape for Sphere { 30 | fn material(&self) -> &Material { 31 | &self.material 32 | } 33 | } 34 | 35 | impl Intersectable for Sphere { 36 | fn intersect(&self, ray: Ray, _: bool) -> Option { 37 | let v = ray.origin - self.origin; 38 | let a = ray.direction.dot(&v); 39 | let b = -a; 40 | let c = a.powf(2.0) - v.length().powf(2.0) + self.radius.powf(2.0); 41 | 42 | if c < 0.0 { 43 | return None; 44 | } 45 | 46 | let t1 = b + c.sqrt(); 47 | let t2 = b - c.sqrt(); 48 | 49 | let (t, hit, inside) = if t1 > 0.01 { 50 | if t2 < 0.0 { 51 | (Some(t1), true, true) 52 | } else { 53 | (Some(t2), true, false) 54 | } 55 | } else { 56 | (None, false, false) 57 | }; 58 | 59 | if hit { 60 | assert!(t.is_some()); 61 | let point: Point3 = (ray.origin + ray.direction * t.unwrap()).as_point(); 62 | let normal = (point - self.origin).normalize(); 63 | let texture_coord = TextureCoord::new( 64 | normal.x.atan2(normal.z) / (2.0 * PI) + 0.5, 65 | normal.y * 0.5 + 0.5, 66 | ); 67 | 68 | let intersection = Intersection::new( 69 | t.unwrap(), 70 | self, 71 | point, 72 | ray, 73 | normal, 74 | inside, 75 | Some(texture_coord), 76 | ); 77 | 78 | return Some(intersection); 79 | } 80 | 81 | None 82 | } 83 | } 84 | 85 | impl Transformable for Sphere { 86 | fn transform(&mut self, transform: &Transform) { 87 | self.origin = transform.matrix * self.origin; 88 | } 89 | } 90 | 91 | #[cfg(test)] 92 | mod tests { 93 | use std::rc::Rc; 94 | 95 | use super::*; 96 | use crate::color::Color; 97 | use crate::geometry::Shape; 98 | use crate::material::{IllumninationModel, Material, MaterialTemplate}; 99 | use crate::math::{Vector3, EPSILON}; 100 | use crate::ray::Ray; 101 | 102 | fn build_test_material() -> Rc { 103 | let color = Color::new(0, 0, 0); 104 | 105 | Rc::new( 106 | MaterialTemplate::new( 107 | color, 108 | color, 109 | color, 110 | 0.0, 111 | IllumninationModel::Constant, 112 | None, 113 | None, 114 | ) 115 | .build_material(|_ignore| {}), 116 | ) 117 | } 118 | 119 | #[test] 120 | fn test_intersection_miss() { 121 | let material = build_test_material(); 122 | let sphere = Sphere::new(Point3::at_origin(), 1.0, material); 123 | let ray = Ray::new( 124 | Point3::new(0.0, 0.0, 2.0), 125 | Vector3::new(0.0, 0.0, 1.0), 126 | None, 127 | ); 128 | 129 | let intersection = (&sphere as &dyn Shape).intersect(ray, false); 130 | 131 | assert!(intersection.is_none()); 132 | } 133 | 134 | #[test] 135 | fn test_intersection() { 136 | let material = build_test_material(); 137 | let sphere = Sphere::new(Point3::at_origin(), 1.0, material); 138 | let ray = Ray::new( 139 | Point3::new(0.0, 0.0, 2.0), 140 | Vector3::new(0.0, 0.0, -1.0), 141 | None, 142 | ); 143 | 144 | let i = (&sphere as &dyn Shape).intersect(ray, false); 145 | assert!(i.is_some()); 146 | 147 | let intersection = i.unwrap(); 148 | 149 | assert_eq_within_bound!(intersection.t, 1.0, EPSILON); 150 | assert_eq_vector3!(intersection.point, Vector3::new(0.0, 0.0, 1.0), EPSILON); 151 | assert_eq_vector3!(intersection.normal, Vector3::new(0.0, 0.0, 1.0), EPSILON); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/geometry/triangle.rs: -------------------------------------------------------------------------------- 1 | use std::rc::Rc; 2 | 3 | use crate::intersection::Intersection; 4 | use crate::material::Material; 5 | use crate::math::EPSILON; 6 | use crate::math::{Point3, Transform, Vector3}; 7 | use crate::ray::Ray; 8 | use crate::texture::TextureCoord; 9 | 10 | use super::{Intersectable, Shape, Transformable}; 11 | 12 | #[cfg(feature = "stats")] 13 | pub mod stats { 14 | use std::sync::atomic::{AtomicUsize, Ordering}; 15 | 16 | static TRIANGLE_INTERSECTION_TEST_COUNT: AtomicUsize = AtomicUsize::new(0); 17 | static TRIANGLE_INTERSECTION_HIT_COUNT: AtomicUsize = AtomicUsize::new(0); 18 | 19 | pub fn record_triangle_intersection() { 20 | TRIANGLE_INTERSECTION_TEST_COUNT.fetch_add(1, Ordering::SeqCst); 21 | } 22 | 23 | pub fn record_triangle_hit() { 24 | TRIANGLE_INTERSECTION_HIT_COUNT.fetch_add(1, Ordering::SeqCst); 25 | } 26 | 27 | pub fn number_of_triangle_intersections() -> usize { 28 | TRIANGLE_INTERSECTION_TEST_COUNT.load(Ordering::SeqCst) 29 | } 30 | 31 | pub fn number_of_triangle_hits() -> usize { 32 | TRIANGLE_INTERSECTION_HIT_COUNT.load(Ordering::SeqCst) 33 | } 34 | } 35 | 36 | #[cfg(feature = "stats")] 37 | fn record_triangle_intersection() { 38 | stats::record_triangle_intersection(); 39 | } 40 | 41 | #[cfg(feature = "stats")] 42 | fn record_triangle_hit() { 43 | stats::record_triangle_hit(); 44 | } 45 | 46 | #[derive(Debug)] 47 | pub enum Normal { 48 | Face(Vector3), 49 | Vertex(Vector3, Vector3, Vector3), 50 | } 51 | 52 | #[derive(Debug)] 53 | pub struct Triangle { 54 | // A, B, C 55 | pub vertices: [Point3; 3], 56 | pub ab: Vector3, // B - A 57 | pub ac: Vector3, // C - A 58 | pub normal: Normal, 59 | // Texture coords for A, b, C 60 | texture_coords: Option<[TextureCoord; 3]>, 61 | material: Rc, 62 | } 63 | 64 | impl Triangle { 65 | pub fn new( 66 | a: Point3, 67 | b: Point3, 68 | c: Point3, 69 | normal: Normal, 70 | texture_coords: Option<[TextureCoord; 3]>, 71 | material: Rc, 72 | ) -> Self { 73 | let ab = b - a; 74 | let ac = c - a; 75 | 76 | Self { 77 | vertices: [a, b, c], 78 | ab, 79 | ac, 80 | normal, 81 | texture_coords, 82 | material, 83 | } 84 | } 85 | } 86 | 87 | impl Shape for Triangle { 88 | fn material(&self) -> &Material { 89 | &self.material 90 | } 91 | } 92 | 93 | impl Intersectable for Triangle { 94 | fn intersect(&self, ray: Ray, cull: bool) -> Option { 95 | #[cfg(feature = "stats")] 96 | record_triangle_intersection(); 97 | 98 | let pvec = ray.direction.cross(&self.ac); 99 | let det = self.ab.dot(&pvec); 100 | 101 | if cull && det < 1e-7 { 102 | return None; 103 | } 104 | 105 | if det.abs() < 1e-7 { 106 | return None; 107 | } 108 | 109 | let inv_det = 1.0 / det; 110 | 111 | let tvec = ray.origin - self.vertices[0]; 112 | let u = tvec.dot(&pvec) * inv_det; 113 | 114 | // This is clearer that what clippy suggests 115 | #[allow(clippy::manual_range_contains)] 116 | if u < 0.0 || u > 1.0 { 117 | return None; 118 | } 119 | 120 | let qvec = tvec.cross(&self.ab); 121 | let v = ray.direction.dot(&qvec) * inv_det; 122 | 123 | if v < 0.0 || u + v > 1.0 { 124 | return None; 125 | } 126 | 127 | let t = self.ac.dot(&qvec) * inv_det; 128 | if t > EPSILON { 129 | let intersection_point = (ray.origin + ray.direction * t).as_point(); 130 | let texture_coord = if let Some([ta, tb, tc]) = self.texture_coords { 131 | let w = 1.0 - v - u; 132 | 133 | Some(TextureCoord::new( 134 | w * ta.x + u * tb.x + v * tc.x, 135 | w * ta.y + u * tb.y + v * tc.y, 136 | )) 137 | } else { 138 | None 139 | }; 140 | 141 | let intersection = Intersection::new( 142 | t, 143 | self, 144 | intersection_point, 145 | ray, 146 | self.normal_at_intersection(u, v), 147 | false, 148 | texture_coord, 149 | ); 150 | 151 | #[cfg(feature = "stats")] 152 | record_triangle_hit(); 153 | return Some(intersection); 154 | } 155 | 156 | None 157 | } 158 | } 159 | 160 | impl Triangle { 161 | fn normal_at_intersection(&self, u: f32, v: f32) -> Vector3 { 162 | match self.normal { 163 | Normal::Face(normal) => normal, 164 | Normal::Vertex(n0, n1, n2) => (n0 * (1.0 - u - v) + n1 * u + n2 * v).normalize(), 165 | } 166 | } 167 | } 168 | 169 | impl Transformable for Triangle { 170 | fn transform(&mut self, transform: &Transform) { 171 | let matrix = transform.matrix; 172 | let normal_matrix = transform.matrix; 173 | 174 | // TODO: Consider doing this as a 4x4 matrix calculation instead 175 | self.vertices[0] = matrix * self.vertices[0]; 176 | self.vertices[1] = matrix * self.vertices[1]; 177 | self.vertices[2] = matrix * self.vertices[2]; 178 | self.ab = self.vertices[1] - self.vertices[0]; 179 | self.ac = self.vertices[2] - self.vertices[0]; 180 | self.normal = match self.normal { 181 | Normal::Face(normal) => Normal::Face((normal_matrix * normal).normalize()), 182 | Normal::Vertex(n0, n1, n2) => Normal::Vertex( 183 | (normal_matrix * n0).normalize(), 184 | (normal_matrix * n1).normalize(), 185 | (normal_matrix * n2).normalize(), 186 | ), 187 | }; 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/intersection.rs: -------------------------------------------------------------------------------- 1 | use crate::geometry::Shape; 2 | use crate::math::{Point3, Vector3}; 3 | use crate::ray::Ray; 4 | use crate::texture::TextureCoord; 5 | 6 | #[derive(Copy, Clone)] 7 | pub struct Intersection<'a> { 8 | pub t: f32, 9 | pub shape: &'a dyn Shape, 10 | pub point: Point3, 11 | pub ray: Ray, 12 | pub normal: Vector3, 13 | pub inside: bool, 14 | pub texture_coord: Option, 15 | } 16 | 17 | impl<'a> Intersection<'a> { 18 | pub fn new( 19 | t: f32, 20 | shape: &'a dyn Shape, 21 | point: Point3, 22 | ray: Ray, 23 | normal: Vector3, 24 | inside: bool, 25 | texture_coord: Option, 26 | ) -> Intersection { 27 | Intersection { 28 | t, 29 | shape, 30 | point, 31 | ray, 32 | normal, 33 | inside, 34 | texture_coord, 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | mod math; 3 | mod config_loader; 4 | mod intersection; 5 | mod light; 6 | mod ray; 7 | 8 | pub mod camera; 9 | pub mod color; 10 | mod config; 11 | pub mod geometry; 12 | pub mod material; 13 | pub mod mesh_loader; 14 | pub mod renderer; 15 | pub mod scene; 16 | pub mod texture; 17 | 18 | pub use self::camera::Camera; 19 | pub use self::color::Color; 20 | pub use self::config::Config; 21 | pub use self::config_loader::ConfigLoader; 22 | pub use self::material::{IllumninationModel, Material, MaterialTemplate}; 23 | pub use self::renderer::{Renderer, SuperSampling}; 24 | pub use self::scene::Scene; 25 | -------------------------------------------------------------------------------- /src/light/directional.rs: -------------------------------------------------------------------------------- 1 | use std::f32; 2 | 3 | use crate::color::Color; 4 | use crate::intersection::Intersection; 5 | use crate::material::Material; 6 | use crate::math::Vector3; 7 | use crate::ray::Ray; 8 | 9 | use super::Light; 10 | 11 | pub struct Directional { 12 | pub direction: Vector3, 13 | inverse_direction: Vector3, 14 | pub color: Color, 15 | intensity: f32, 16 | diffuse: bool, 17 | specular: bool, 18 | } 19 | 20 | impl Directional { 21 | pub fn new( 22 | direction: Vector3, 23 | color: Color, 24 | intensity: f32, 25 | diffuse: bool, 26 | specular: bool, 27 | ) -> Self { 28 | let normalized_direction = direction.normalize(); 29 | Self { 30 | direction: normalized_direction, 31 | inverse_direction: -normalized_direction, 32 | color, 33 | intensity, 34 | diffuse, 35 | specular, 36 | } 37 | } 38 | 39 | pub fn intensity(&self, _distance_to_light: f32) -> f32 { 40 | self.intensity 41 | } 42 | } 43 | 44 | impl Light for Directional { 45 | fn create_shadow_ray( 46 | &self, 47 | intersection: &Intersection, 48 | medium_refraction: Option, 49 | ) -> Ray { 50 | let direction = self.inverse_direction; 51 | Ray::new( 52 | (intersection.point + direction * 1e-3).as_point(), 53 | direction, 54 | medium_refraction, 55 | ) 56 | } 57 | 58 | fn distance_to_light(&self, _intersection: &Intersection) -> f32 { 59 | f32::INFINITY 60 | } 61 | 62 | fn diffuse_color( 63 | &self, 64 | intersection: &Intersection, 65 | material: &Material, 66 | distance_to_light: f32, 67 | ) -> Option { 68 | if !self.diffuse { 69 | return None; 70 | } 71 | 72 | let dot = self.inverse_direction.dot(&intersection.normal); 73 | 74 | if dot > 0.0 { 75 | Some( 76 | (self.color * material.diffuse_color(intersection.texture_coord)) 77 | * dot 78 | * self.intensity(distance_to_light), 79 | ) 80 | } else { 81 | None 82 | } 83 | } 84 | 85 | fn specular_color( 86 | &self, 87 | intersection: &Intersection, 88 | material: &Material, 89 | ray: &Ray, 90 | distance_to_light: f32, 91 | ) -> Option { 92 | if !self.specular { 93 | return None; 94 | } 95 | 96 | let dot = ray 97 | .direction 98 | .dot(&self.inverse_direction.reflect(&intersection.normal)); 99 | 100 | if dot > 0.0 { 101 | let spec = dot.powf(material.specular_exponent); 102 | Some( 103 | (self.color * material.specular_color(intersection.texture_coord)) 104 | * spec 105 | * self.intensity(distance_to_light), 106 | ) 107 | } else { 108 | None 109 | } 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/light/mod.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | use crate::color::Color; 4 | use crate::intersection::Intersection; 5 | use crate::material::Material; 6 | use crate::ray::Ray; 7 | 8 | pub mod directional; 9 | pub mod point; 10 | 11 | pub use self::directional::Directional; 12 | pub use self::point::Point; 13 | 14 | #[derive(Deserialize, Debug, Copy, Clone)] 15 | pub enum Falloff { 16 | // Light falls off with the inverse square of the distance 17 | InverseSquare, 18 | // Light falls off linearly with the distance 19 | InverseLinear, 20 | } 21 | 22 | pub trait Light { 23 | fn create_shadow_ray(&self, intersection: &Intersection, medium_refraction: Option) 24 | -> Ray; 25 | fn distance_to_light(&self, intersection: &Intersection) -> f32; 26 | fn diffuse_color( 27 | &self, 28 | intersection: &Intersection, 29 | material: &Material, 30 | distance_to_light: f32, 31 | ) -> Option; 32 | fn specular_color( 33 | &self, 34 | intersection: &Intersection, 35 | material: &Material, 36 | ray: &Ray, 37 | distance_to_light: f32, 38 | ) -> Option; 39 | } 40 | -------------------------------------------------------------------------------- /src/light/point.rs: -------------------------------------------------------------------------------- 1 | use crate::color::Color; 2 | use crate::intersection::Intersection; 3 | use crate::material::Material; 4 | use crate::math::Point3; 5 | use crate::ray::Ray; 6 | 7 | use super::{Falloff, Light}; 8 | 9 | pub struct Point { 10 | pub origin: Point3, 11 | pub color: Color, 12 | intensity: f32, 13 | falloff: Falloff, 14 | diffuse: bool, 15 | specular: bool, 16 | } 17 | 18 | impl Point { 19 | pub fn new( 20 | origin: Point3, 21 | color: Color, 22 | intensity: f32, 23 | falloff: Falloff, 24 | diffuse: bool, 25 | specular: bool, 26 | ) -> Self { 27 | Self { 28 | origin, 29 | color, 30 | intensity, 31 | falloff, 32 | diffuse, 33 | specular, 34 | } 35 | } 36 | 37 | pub fn intensity(&self, distance_to_light: f32) -> f32 { 38 | match self.falloff { 39 | Falloff::InverseSquare => { 40 | 1.0 / (distance_to_light * distance_to_light) * self.intensity 41 | } 42 | Falloff::InverseLinear => 1.0 / distance_to_light * self.intensity, 43 | } 44 | } 45 | } 46 | 47 | impl Light for Point { 48 | fn create_shadow_ray( 49 | &self, 50 | intersection: &Intersection, 51 | medium_refraction: Option, 52 | ) -> Ray { 53 | let light_direction = (self.origin - intersection.point).normalize(); 54 | Ray::new( 55 | (intersection.point + light_direction * 1e-3).as_point(), 56 | light_direction, 57 | medium_refraction, 58 | ) 59 | } 60 | 61 | fn distance_to_light(&self, intersection: &Intersection) -> f32 { 62 | (intersection.point - self.origin).length() 63 | } 64 | 65 | fn diffuse_color( 66 | &self, 67 | intersection: &Intersection, 68 | material: &Material, 69 | distance_to_light: f32, 70 | ) -> Option { 71 | if !self.diffuse { 72 | return None; 73 | } 74 | 75 | let light_direction = (self.origin - intersection.point).normalize(); 76 | let dot = light_direction.dot(&intersection.normal); 77 | 78 | if dot > 0.0 { 79 | Some( 80 | (self.color * material.diffuse_color(intersection.texture_coord)) 81 | * dot 82 | * self.intensity(distance_to_light), 83 | ) 84 | } else { 85 | None 86 | } 87 | } 88 | 89 | fn specular_color( 90 | &self, 91 | intersection: &Intersection, 92 | material: &Material, 93 | ray: &Ray, 94 | distance_to_light: f32, 95 | ) -> Option { 96 | if !self.specular { 97 | return None; 98 | } 99 | 100 | let light_direction = (self.origin - intersection.point).normalize(); 101 | let dot = ray 102 | .direction 103 | .dot(&light_direction.reflect(&intersection.normal)); 104 | 105 | if dot > 0.0 { 106 | let spec = dot.powf(material.specular_exponent); 107 | Some( 108 | (self.color * material.specular_color(intersection.texture_coord)) 109 | * spec 110 | * self.intensity(distance_to_light), 111 | ) 112 | } else { 113 | None 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use std::f32::consts::PI; 3 | use std::path::Path; 4 | use std::rc::Rc; 5 | use std::time::SystemTime; 6 | 7 | use getopts::Options; 8 | 9 | #[cfg(feature = "stats")] 10 | use rusttracer::geometry::triangle::stats; 11 | use rusttracer::texture; 12 | use rusttracer::{Color, ConfigLoader, IllumninationModel, MaterialTemplate}; 13 | 14 | #[cfg(feature = "stats")] 15 | fn print_triangle_stats() { 16 | use tracing::info; 17 | 18 | let number_of_tests = stats::number_of_triangle_intersections(); 19 | let number_of_hits = stats::number_of_triangle_hits(); 20 | 21 | info!("Total number of ray-triangle tests: {}", number_of_tests); 22 | info!("Total number of ray-triangle hits: {}", number_of_hits); 23 | info!( 24 | "Efficiency: {:.5}%", 25 | (f64::from(number_of_hits as u32) / f64::from(number_of_tests as u32)) * 100.0 26 | ); 27 | } 28 | 29 | fn print_usage(program: &str, opts: &Options) { 30 | let brief = format!("Usage: {} [options]", program); 31 | print!("{}", opts.usage(&brief)); 32 | } 33 | 34 | fn main() -> Result<(), Box> { 35 | tracing_subscriber::fmt::init(); 36 | 37 | let args: Vec = env::args().collect(); 38 | 39 | let program = args[0].clone(); 40 | let mut opts = Options::new(); 41 | opts.optopt("c", "config-path", "config file path", "CONFIG_PATH"); 42 | opts.optflag( 43 | "b", 44 | "benchmark", 45 | "Benchmark by rendering the scene multiple times", 46 | ); 47 | opts.optflag("h", "help", "prints this help menu"); 48 | 49 | let matches = match opts.parse(&args[1..]) { 50 | Ok(m) => m, 51 | Err(f) => panic!("{}", f), 52 | }; 53 | 54 | if matches.opt_present("h") { 55 | print_usage(&program, &opts); 56 | return Ok(()); 57 | } 58 | 59 | let benchmark = matches.opt_present("b"); 60 | let config_path = matches.opt_str("c").expect("No config provided"); 61 | 62 | let template = MaterialTemplate::new( 63 | Color::blue() * 0.02, 64 | Color::black(), 65 | Color::black(), 66 | 0.0, 67 | IllumninationModel::DiffuseSpecular, 68 | None, 69 | None, 70 | ); 71 | let fallback_material = Rc::new(template.build_material(|material| { 72 | material.ambient_color = Color::white() * 0.05; 73 | material.diffuse_color = Color::white() * 0.0; 74 | material.ambient_texture = Some(Rc::new(texture::Procedural::new(|uv| { 75 | Color::new_f32( 76 | ((uv.x * 32.0 * PI).sin() + (uv.y * 32.0 * PI).cos() + 1.0) * 0.5, 77 | 0.0, 78 | 0.0, 79 | ) 80 | }))); 81 | })); 82 | 83 | let mut config_loader = ConfigLoader::new(fallback_material); 84 | config_loader.register_named_texture("mandelbrot", Rc::new(texture::Procedural::mandelbrot())); 85 | config_loader.register_named_texture("julia", Rc::new(texture::Procedural::julia())); 86 | config_loader 87 | .register_named_texture("checkerboard", Rc::new(texture::Procedural::checkerboard())); 88 | 89 | let (renderer, config) = config_loader.load_renderer_from_config(&config_path)?; 90 | let camera_config = config.cameras.first().unwrap(); 91 | 92 | if benchmark { 93 | for _ in 0..10 { 94 | let _ = renderer.render(config.max_depth); 95 | } 96 | 97 | return Ok(()); 98 | } 99 | 100 | let buffer = renderer.render(config.max_depth); 101 | 102 | #[cfg(feature = "stats")] 103 | print_triangle_stats(); 104 | 105 | let now = SystemTime::now(); 106 | let timestamp = now.duration_since(SystemTime::UNIX_EPOCH)?.as_secs(); 107 | let filename = format!("images/{}.png", timestamp); 108 | image::save_buffer( 109 | Path::new(&filename), 110 | &buffer[..], 111 | camera_config.width, 112 | camera_config.height, 113 | image::ColorType::Rgb8, 114 | ) 115 | .unwrap(); 116 | 117 | Ok(()) 118 | } 119 | -------------------------------------------------------------------------------- /src/material.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | use std::error::Error; 3 | use std::fmt; 4 | use std::rc::Rc; 5 | 6 | use serde::Deserialize; 7 | 8 | use crate::color::Color; 9 | use crate::texture::{Texture, TextureCoord}; 10 | 11 | #[derive(Debug)] 12 | pub struct IllumninationModelParsingError { 13 | invalid_model: u8, 14 | } 15 | 16 | impl IllumninationModelParsingError { 17 | fn new(invalid_model: u8) -> Self { 18 | Self { invalid_model } 19 | } 20 | } 21 | 22 | impl fmt::Display for IllumninationModelParsingError { 23 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 24 | write!( 25 | f, 26 | "Failed to parse illumination model: {}", 27 | self.invalid_model 28 | ) 29 | } 30 | } 31 | 32 | impl Error for IllumninationModelParsingError { 33 | fn source(&self) -> Option<&(dyn Error + 'static)> { 34 | None 35 | } 36 | } 37 | 38 | /// See http://paulbourke.net/dataformats/mtl/ 39 | #[derive(Deserialize, Debug, Copy, Clone, PartialEq, Eq)] 40 | pub enum IllumninationModel { 41 | /// Mode 0, constant diffuse color 42 | Constant = 0, 43 | /// Mode 1, diffuse lambertian shading 44 | Diffuse = 1, 45 | /// Mode 2, diffuse specular using lambertian shading 46 | /// and Blinn's Phong 47 | DiffuseSpecular = 2, 48 | /// Mode 3, diffuse specular with reflection using lambertian shading, Blinn's Phong and 49 | /// reflection. 50 | DiffuseSpecularReflective = 3, 51 | /// Mode 4, diffuse specular with reflection emulating glass using lambertian shading, Blinn's Phong and 52 | /// reflection. 53 | DiffuseSpecularReflectiveGlass = 4, 54 | /// Mode 5, diffuse specular with fresnel reflection using lambertian shading, Blinn's Phong and 55 | /// fresnel reflection. 56 | DiffuseSpecularFresnel = 5, 57 | /// Mode 6, diffuse specular using lambertian shading, Blinn's Phong and 58 | /// supporting refraction. 59 | DiffuseSpecularRefracted = 6, 60 | /// Mode 7, diffuse specular with reflections using lambertian shading, Blinn's Phong and 61 | /// Fresnel refraction/reflection. 62 | DiffuseSpecularRefractedFresnel = 7, 63 | } 64 | 65 | impl TryFrom for IllumninationModel { 66 | type Error = IllumninationModelParsingError; 67 | 68 | fn try_from(value: u8) -> Result { 69 | match value { 70 | 0 => Ok(IllumninationModel::Constant), 71 | 1 => Ok(IllumninationModel::Diffuse), 72 | 2 => Ok(IllumninationModel::DiffuseSpecular), 73 | 3 => Ok(IllumninationModel::DiffuseSpecularReflective), 74 | 4 => Ok(IllumninationModel::DiffuseSpecularReflectiveGlass), 75 | 5 => Ok(IllumninationModel::DiffuseSpecularFresnel), 76 | 6 => Ok(IllumninationModel::DiffuseSpecularRefracted), 77 | 7 => Ok(IllumninationModel::DiffuseSpecularRefractedFresnel), 78 | _ => Err(IllumninationModelParsingError::new(value)), 79 | } 80 | } 81 | } 82 | 83 | pub type OptionalTexture = Option>; 84 | 85 | #[derive(Debug)] 86 | pub struct Material { 87 | pub ambient_color: Color, 88 | pub ambient_texture: OptionalTexture, 89 | pub diffuse_color: Color, 90 | pub diffuse_texture: OptionalTexture, 91 | pub specular_color: Color, 92 | pub specular_texture: OptionalTexture, 93 | pub specular_exponent: f32, 94 | pub illumination_model: IllumninationModel, 95 | pub reflection_coefficient: Option, 96 | pub refraction_coefficient: Option, 97 | } 98 | 99 | impl Material { 100 | pub fn is_reflective(&self) -> bool { 101 | self.reflection_coefficient.is_some() 102 | && (self.illumination_model == IllumninationModel::DiffuseSpecularReflective 103 | || self.illumination_model == IllumninationModel::DiffuseSpecularReflectiveGlass 104 | || self.illumination_model == IllumninationModel::DiffuseSpecularRefractedFresnel) 105 | } 106 | 107 | pub fn is_refractive(&self) -> bool { 108 | self.refraction_coefficient.is_some() 109 | && (self.illumination_model == IllumninationModel::DiffuseSpecularRefracted 110 | || self.illumination_model == IllumninationModel::DiffuseSpecularRefractedFresnel) 111 | } 112 | 113 | pub fn new( 114 | ambient_color: Color, 115 | diffuse_color: Color, 116 | specular_color: Color, 117 | specular_exponent: f32, 118 | illumination_model: IllumninationModel, 119 | reflection_coefficient: Option, 120 | refraction_coefficient: Option, 121 | ) -> Self { 122 | Material { 123 | ambient_color, 124 | ambient_texture: None, 125 | diffuse_color, 126 | diffuse_texture: None, 127 | specular_color, 128 | specular_texture: None, 129 | specular_exponent, 130 | illumination_model, 131 | reflection_coefficient, 132 | refraction_coefficient, 133 | } 134 | } 135 | 136 | #[allow(clippy::too_many_arguments)] 137 | pub fn new_with_textures( 138 | ambient_color: Color, 139 | ambient_texture: OptionalTexture, 140 | diffuse_color: Color, 141 | diffuse_texture: OptionalTexture, 142 | specular_color: Color, 143 | specular_texture: OptionalTexture, 144 | specular_exponent: f32, 145 | illumination_model: IllumninationModel, 146 | reflection_coefficient: Option, 147 | refraction_coefficient: Option, 148 | ) -> Self { 149 | Material { 150 | ambient_color, 151 | ambient_texture, 152 | diffuse_color, 153 | diffuse_texture, 154 | specular_color, 155 | specular_texture, 156 | specular_exponent, 157 | illumination_model, 158 | reflection_coefficient, 159 | refraction_coefficient, 160 | } 161 | } 162 | 163 | pub fn ambient_color(&self, uv: Option) -> Color { 164 | match &self.ambient_texture { 165 | None => self.ambient_color, 166 | Some(texture) => uv.map_or(self.ambient_color, |coord| { 167 | self.ambient_color * texture.lookup(coord) 168 | }), 169 | } 170 | } 171 | 172 | pub fn diffuse_color(&self, uv: Option) -> Color { 173 | match &self.diffuse_texture { 174 | None => self.diffuse_color, 175 | Some(texture) => uv.map_or(self.diffuse_color, |coord| { 176 | self.diffuse_color * texture.lookup(coord) 177 | }), 178 | } 179 | } 180 | 181 | pub fn specular_color(&self, uv: Option) -> Color { 182 | match &self.specular_texture { 183 | None => self.specular_color, 184 | Some(texture) => uv.map_or(self.specular_color, |coord| { 185 | self.specular_color * texture.lookup(coord) 186 | }), 187 | } 188 | } 189 | } 190 | 191 | pub struct MaterialTemplate { 192 | ambient_color: Color, 193 | diffuse_color: Color, 194 | specular_color: Color, 195 | specular_exponent: f32, 196 | illumination_model: IllumninationModel, 197 | reflection_coefficient: Option, 198 | refraction_coefficient: Option, 199 | } 200 | 201 | impl MaterialTemplate { 202 | pub fn build_material(&self, builder_closure: F) -> Material 203 | where 204 | F: Fn(&mut Material), 205 | { 206 | let mut material = Material::new( 207 | self.ambient_color, 208 | self.diffuse_color, 209 | self.specular_color, 210 | self.specular_exponent, 211 | self.illumination_model, 212 | self.reflection_coefficient, 213 | self.refraction_coefficient, 214 | ); 215 | 216 | builder_closure(&mut material); 217 | 218 | material 219 | } 220 | 221 | pub fn new( 222 | ambient_color: Color, 223 | diffuse_color: Color, 224 | specular_color: Color, 225 | specular_exponent: f32, 226 | illumination_model: IllumninationModel, 227 | reflection_coefficient: Option, 228 | refraction_coefficient: Option, 229 | ) -> MaterialTemplate { 230 | MaterialTemplate { 231 | ambient_color, 232 | diffuse_color, 233 | specular_color, 234 | illumination_model, 235 | specular_exponent, 236 | reflection_coefficient, 237 | refraction_coefficient, 238 | } 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/math/complex.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, Sub}; 2 | 3 | #[derive(Debug, Copy, Clone)] 4 | pub struct Complex { 5 | pub real: f64, 6 | pub im: f64, 7 | } 8 | 9 | impl Complex { 10 | pub const fn new(real: f64, im: f64) -> Self { 11 | Self { real, im } 12 | } 13 | 14 | pub const fn zero() -> Self { 15 | Self { real: 0.0, im: 0.0 } 16 | } 17 | 18 | pub fn add_mut(&mut self, other: &Self) { 19 | self.real += other.real; 20 | self.im += other.im; 21 | } 22 | 23 | pub fn square_mut(&mut self) { 24 | let temp = (self.real * self.real) - (self.im * self.im); 25 | let im = 2.0 * self.real * self.im; 26 | let real = temp; 27 | 28 | self.real = real; 29 | self.im = im; 30 | } 31 | 32 | pub fn dot(&self) -> f64 { 33 | (self.real * self.real) + (self.im * self.im) 34 | } 35 | 36 | pub fn abs(&self) -> f64 { 37 | self.dot().sqrt() 38 | } 39 | } 40 | 41 | impl Default for Complex { 42 | fn default() -> Self { 43 | Self::zero() 44 | } 45 | } 46 | 47 | impl Add for Complex { 48 | type Output = Self; 49 | 50 | fn add(self, other: Self) -> Self { 51 | Self { 52 | real: self.real + other.real, 53 | im: self.im + other.im, 54 | } 55 | } 56 | } 57 | 58 | impl Sub for Complex { 59 | type Output = Self; 60 | 61 | fn sub(self, other: Self) -> Self { 62 | Self { 63 | real: self.real - other.real, 64 | im: self.im - other.im, 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/math/matrix4.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Index, IndexMut, Mul}; 2 | 3 | use super::EPSILON; 4 | 5 | #[derive(Debug, Copy, Clone)] 6 | pub struct Matrix4 { 7 | // Rows in the outer slice and columns in the inner 8 | data: [[f32; 4]; 4], 9 | } 10 | 11 | impl Matrix4 { 12 | pub fn new(data: [[f32; 4]; 4]) -> Self { 13 | Matrix4 { data } 14 | } 15 | 16 | pub fn transpose(&self) -> Self { 17 | Self::new([ 18 | [self[(0, 0)], self[(1, 0)], self[(2, 0)], self[(3, 0)]], 19 | [self[(0, 1)], self[(1, 1)], self[(2, 1)], self[(3, 1)]], 20 | [self[(0, 2)], self[(1, 2)], self[(2, 2)], self[(3, 2)]], 21 | [self[(0, 3)], self[(1, 3)], self[(2, 3)], self[(3, 3)]], 22 | ]) 23 | } 24 | 25 | pub fn inverse(&self) -> Result { 26 | let mut result = Matrix4::identity(); 27 | let mut self_copy = *self; 28 | 29 | for column in 0..4 { 30 | if self_copy[(column, column)].abs() < EPSILON { 31 | let mut larger = column; 32 | 33 | for row in 0..4 { 34 | if self_copy[(row, column)].abs() > self_copy[(larger, column)].abs() { 35 | larger = row; 36 | } 37 | } 38 | 39 | if larger == column { 40 | return Err("Singular matrix, cannot be inverted"); 41 | } 42 | 43 | self_copy.data.swap(column, larger); 44 | result.data.swap(column, larger); 45 | } 46 | 47 | for row in 0..4 { 48 | if row == column { 49 | continue; 50 | } 51 | 52 | let coeff = self_copy[(row, column)] / self_copy[(column, column)]; 53 | 54 | if coeff != 0.0 { 55 | for j in 0..4 { 56 | self_copy[(row, j)] -= coeff * self_copy[(column, j)]; 57 | result[(row, j)] -= coeff * result[(column, j)]; 58 | } 59 | 60 | self_copy[(row, column)] = 0.0; 61 | } 62 | } 63 | } 64 | 65 | for row in 0..4 { 66 | for column in 0..4 { 67 | result[(row, column)] /= self_copy[(row, row)]; 68 | } 69 | } 70 | 71 | Ok(result) 72 | } 73 | 74 | pub fn identity() -> Self { 75 | Self::new([ 76 | [1.0, 0.0, 0.0, 0.0], 77 | [0.0, 1.0, 0.0, 0.], 78 | [0.0, 0.0, 1.0, 0.0], 79 | [0.0, 0.0, 0.0, 1.0], 80 | ]) 81 | } 82 | 83 | pub fn translate(x: f32, y: f32, z: f32) -> Self { 84 | Self::new([ 85 | [1.0, 0.0, 0.0, 0.0], 86 | [0.0, 1.0, 0.0, 0.0], 87 | [0.0, 0.0, 1.0, 0.0], 88 | [x, y, z, 1.0], 89 | ]) 90 | } 91 | 92 | pub fn scale(x: f32, y: f32, z: f32) -> Self { 93 | Self::new([ 94 | [x, 0.0, 0.0, 0.0], 95 | [0.0, y, 0.0, 0.], 96 | [0.0, 0.0, z, 0.0], 97 | [0.0, 0.0, 0.0, 1.0], 98 | ]) 99 | } 100 | 101 | pub fn scale_uniform(scale: f32) -> Self { 102 | Self::scale(scale, scale, scale) 103 | } 104 | 105 | pub fn rot_x(theta: f32) -> Self { 106 | Self::new([ 107 | [1.0, 0.0, 0.0, 0.0], 108 | [0.0, theta.cos(), theta.sin(), 0.0], 109 | [0.0, -theta.sin(), theta.cos(), 0.0], 110 | [0.0, 0.0, 0.0, 1.0], 111 | ]) 112 | } 113 | 114 | pub fn rot_y(theta: f32) -> Self { 115 | Self::new([ 116 | [theta.cos(), 0.0, -theta.sin(), 0.0], 117 | [0.0, 1.0, 0.0, 0.0], 118 | [theta.sin(), 0.0, theta.cos(), 0.0], 119 | [0.0, 0.0, 0.0, 1.0], 120 | ]) 121 | } 122 | 123 | pub fn rot_z(theta: f32) -> Self { 124 | Self::new([ 125 | [theta.cos(), theta.sin(), 0.0, 0.0], 126 | [-theta.sin(), theta.cos(), 0.0, 0.0], 127 | [0.0, 0.0, 1.0, 0.0], 128 | [0.0, 0.0, 0.0, 1.0], 129 | ]) 130 | } 131 | } 132 | 133 | impl Index for Matrix4 { 134 | type Output = [f32; 4]; 135 | 136 | fn index(&self, index: usize) -> &[f32; 4] { 137 | &self.data[index] 138 | } 139 | } 140 | 141 | impl Index<(usize, usize)> for Matrix4 { 142 | type Output = f32; 143 | 144 | fn index(&self, index: (usize, usize)) -> &f32 { 145 | &self.data[index.0][index.1] 146 | } 147 | } 148 | 149 | impl IndexMut<(usize, usize)> for Matrix4 { 150 | fn index_mut(&mut self, index: (usize, usize)) -> &mut f32 { 151 | &mut self.data[index.0][index.1] 152 | } 153 | } 154 | 155 | impl Mul for Matrix4 { 156 | type Output = Matrix4; 157 | 158 | fn mul(self, other: Matrix4) -> Matrix4 { 159 | let mut result = Self::identity(); 160 | 161 | for i in 0..4 { 162 | for j in 0..4 { 163 | result[(i, j)] = self[(i, 0)] * other[(0, j)] 164 | + self[(i, 1)] * other[(1, j)] 165 | + self[(i, 2)] * other[(2, j)] 166 | + self[(i, 3)] * other[(3, j)]; 167 | } 168 | } 169 | 170 | result 171 | } 172 | } 173 | 174 | #[cfg(test)] 175 | macro_rules! assert_eq_matrix4 { 176 | ($x:expr, $y:expr, $bound:expr) => { 177 | assert_eq_within_bound!($x[(0, 0)], $y[(0, 0)], $bound); 178 | assert_eq_within_bound!($x[(0, 1)], $y[(0, 1)], $bound); 179 | assert_eq_within_bound!($x[(0, 2)], $y[(0, 2)], $bound); 180 | assert_eq_within_bound!($x[(1, 0)], $y[(1, 0)], $bound); 181 | assert_eq_within_bound!($x[(1, 1)], $y[(1, 1)], $bound); 182 | assert_eq_within_bound!($x[(1, 2)], $y[(1, 2)], $bound); 183 | assert_eq_within_bound!($x[(2, 0)], $y[(2, 0)], $bound); 184 | assert_eq_within_bound!($x[(2, 1)], $y[(2, 1)], $bound); 185 | assert_eq_within_bound!($x[(2, 2)], $y[(2, 2)], $bound); 186 | assert_eq_within_bound!($x[(3, 0)], $y[(3, 0)], $bound); 187 | assert_eq_within_bound!($x[(3, 1)], $y[(3, 1)], $bound); 188 | assert_eq_within_bound!($x[(3, 2)], $y[(3, 2)], $bound); 189 | assert_eq_within_bound!($x[(3, 3)], $y[(3, 3)], $bound); 190 | }; 191 | } 192 | 193 | #[cfg(test)] 194 | mod tests { 195 | use super::Matrix4; 196 | use std::f32::consts::PI; 197 | 198 | const EPSILON: f32 = 1e-3; 199 | 200 | #[test] 201 | fn test_identity() { 202 | let i = Matrix4::identity(); 203 | 204 | let expected = Matrix4::new([ 205 | [1.0, 0.0, 0.0, 0.0], 206 | [0.0, 1.0, 0.0, 0.0], 207 | [0.0, 0.0, 1.0, 0.0], 208 | [0.0, 0.0, 0.0, 1.0], 209 | ]); 210 | 211 | assert_eq_matrix4!(i, expected, EPSILON); 212 | } 213 | 214 | #[test] 215 | fn test_new() { 216 | let m = Matrix4 { 217 | data: [ 218 | [1.0, 0.0, 0.0, 0.0], 219 | [0.0, 1.0, 0.0, 0.0], 220 | [0.0, 0.0, 1.0, 0.0], 221 | [0.0, 0.0, 0.0, 1.0], 222 | ], 223 | }; 224 | 225 | let expected = Matrix4::new([ 226 | [1.0, 0.0, 0.0, 0.0], 227 | [0.0, 1.0, 0.0, 0.0], 228 | [0.0, 0.0, 1.0, 0.0], 229 | [0.0, 0.0, 0.0, 1.0], 230 | ]); 231 | 232 | assert_eq_matrix4!(m, expected, EPSILON); 233 | } 234 | 235 | #[test] 236 | fn transpose_identity() { 237 | let m = Matrix4::identity(); 238 | 239 | let expected = Matrix4::identity(); 240 | 241 | assert_eq_matrix4!(m.transpose(), expected, EPSILON); 242 | } 243 | 244 | #[test] 245 | fn transpose_complex() { 246 | let m = Matrix4::new([ 247 | [1.0, 2.0, 3.0, 4.0], 248 | [5.0, 6.0, 7.0, 8.0], 249 | [9.0, 10.0, 11.0, 12.0], 250 | [13.0, 14.0, 15.0, 16.0], 251 | ]); 252 | 253 | let expected = Matrix4::new([ 254 | [1.0, 5.0, 9.0, 13.0], 255 | [2.0, 6.0, 10.0, 14.0], 256 | [3.0, 7.0, 11.0, 15.0], 257 | [4.0, 8.0, 12.0, 16.0], 258 | ]); 259 | 260 | assert_eq_matrix4!(m.transpose(), expected, EPSILON); 261 | } 262 | 263 | #[test] 264 | fn inverse_identity() { 265 | let m = Matrix4::identity(); 266 | 267 | let inverse = m.inverse().expect("Identity matrix should be invertible"); 268 | 269 | assert_eq_matrix4!(inverse, m, EPSILON); 270 | } 271 | 272 | #[test] 273 | fn inverse_moderate() { 274 | let m = Matrix4::new([ 275 | [2.0, 3.0, 1.0, 5.0], 276 | [1.0, 0.0, 3.0, 1.0], 277 | [0.0, 2.0, -3.0, 2.0], 278 | [0.0, 2.0, 3.0, 1.0], 279 | ]); 280 | 281 | let expected = Matrix4::new([ 282 | [18.0, -35.0, -28.0, 1.0], 283 | [9.0, -18.0, -14.0, 1.0], 284 | [-2.0, 4.0, 3.0, 0.0], 285 | [-12.0, 24.0, 19.0, -1.0], 286 | ]); 287 | 288 | assert_eq_matrix4!(m * expected, Matrix4::identity(), 1e-4); 289 | 290 | let inverse = m 291 | .inverse() 292 | .unwrap_or_else(|e| panic!("{:?} should be invertible: {}", m, e)); 293 | 294 | assert_eq_matrix4!(inverse, expected, 1e-4); 295 | } 296 | 297 | #[test] 298 | fn inverse_complex() { 299 | let matrices = [ 300 | Matrix4::new([ 301 | [2.0, 3.0, 1.0, 5.0], 302 | [1.0, 0.0, 3.0, 1.0], 303 | [0.0, 2.0, -3.0, 2.0], 304 | [0.0, 2.0, 3.0, 1.0], 305 | ]), 306 | Matrix4::rot_x(PI / 2.0), 307 | Matrix4::rot_y(PI / 2.0), 308 | Matrix4::new([ 309 | [1.0, 1.0, 1.0, 0.0], 310 | [0.0, 3.0, 1.0, 2.0], 311 | [2.0, 3.0, 1.0, 0.0], 312 | [1.0, 0.0, 2.0, 1.0], 313 | ]), 314 | ]; 315 | let identity = Matrix4::identity(); 316 | 317 | for matrix in matrices.iter() { 318 | let inverse = matrix 319 | .inverse() 320 | .unwrap_or_else(|e| panic!("{:?} should be invertible: {}", matrix, e)); 321 | println!("Testing {:?}", matrix); 322 | 323 | assert_eq_matrix4!((inverse * *matrix), identity, EPSILON); 324 | } 325 | } 326 | 327 | #[test] 328 | fn index_row() { 329 | let m = Matrix4::identity(); 330 | 331 | let rows = [m[0], m[1], m[2], m[3]]; 332 | 333 | for (i, item) in rows.iter().enumerate() { 334 | assert_eq_within_bound!(item[i], 1.0, EPSILON); 335 | } 336 | } 337 | 338 | #[test] 339 | fn index() { 340 | let m = Matrix4::identity(); 341 | 342 | for i in 0..4 { 343 | assert_eq_within_bound!(m[(i, i)], 1.0, EPSILON); 344 | } 345 | } 346 | 347 | #[test] 348 | fn index_mut() { 349 | let mut m = Matrix4::identity(); 350 | 351 | for i in 0..4 { 352 | m[(3, i)] = 4.5 353 | } 354 | 355 | for i in 0..4 { 356 | assert_eq_within_bound!(m[(3, i)], 4.5, EPSILON); 357 | } 358 | } 359 | 360 | #[test] 361 | fn test_translate() { 362 | let m = Matrix4::translate(-2.0, 3.0, 5.0); 363 | 364 | let expected = Matrix4::new([ 365 | [1.0, 0.0, 0.0, 0.0], 366 | [0.0, 1.0, 0.0, 0.0], 367 | [0.0, 0.0, 1.0, 0.0], 368 | [-2.0, 3.0, 5.0, 1.0], 369 | ]); 370 | 371 | assert_eq_matrix4!(m, expected, EPSILON); 372 | } 373 | 374 | #[test] 375 | fn test_scale() { 376 | let m = Matrix4::scale(5.0, 3.0, -1.0); 377 | 378 | let expected = Matrix4::new([ 379 | [5.0, 0.0, 0.0, 0.0], 380 | [0.0, 3.0, 0.0, 0.0], 381 | [0.0, 0.0, -1.0, 0.0], 382 | [0.0, 0.0, 0.0, 1.0], 383 | ]); 384 | 385 | assert_eq_matrix4!(m, expected, EPSILON); 386 | } 387 | 388 | #[test] 389 | fn test_rot_x() { 390 | let m = Matrix4::rot_x(PI / 2.0); 391 | 392 | let expected = Matrix4::new([ 393 | [1.0, 0.0, 0.0, 0.0], 394 | [0.0, 0.0, 1.0, 0.0], 395 | [0.0, -1.0, 0.0, 0.0], 396 | [0.0, 0.0, 0.0, 1.0], 397 | ]); 398 | 399 | assert_eq_matrix4!(m, expected, EPSILON); 400 | } 401 | 402 | #[test] 403 | fn test_rot_y() { 404 | let m = Matrix4::rot_y(PI / 2.0); 405 | 406 | let expected = Matrix4::new([ 407 | [0.0, 0.0, -1.0, 0.0], 408 | [0.0, 1.0, 0.0, 0.0], 409 | [1.0, 0.0, 0.0, 0.0], 410 | [0.0, 0.0, 0.0, 1.0], 411 | ]); 412 | 413 | assert_eq_matrix4!(m, expected, EPSILON); 414 | } 415 | 416 | #[test] 417 | fn test_rot_z() { 418 | let m = Matrix4::rot_z(PI / 2.0); 419 | 420 | let expected = Matrix4::new([ 421 | [0.0, 1.0, 0.0, 0.0], 422 | [-1.0, 0.0, 0.0, 0.0], 423 | [0.0, 0.0, 1.0, 0.0], 424 | [0.0, 0.0, 0.0, 1.0], 425 | ]); 426 | 427 | assert_eq_matrix4!(m, expected, EPSILON); 428 | } 429 | } 430 | -------------------------------------------------------------------------------- /src/math/mod.rs: -------------------------------------------------------------------------------- 1 | pub const EPSILON: f32 = 1e-6; 2 | 3 | #[cfg(test)] 4 | macro_rules! assert_eq_within_bound { 5 | ($x:expr, $y:expr, $bound:expr) => { 6 | assert!( 7 | $x >= $y - $bound && $x <= $y + $bound, 8 | "{} is not equal to {} within bound {}", 9 | $x, 10 | $y, 11 | $bound 12 | ); 13 | }; 14 | } 15 | 16 | #[cfg(test)] 17 | macro_rules! assert_eq_vector2 { 18 | ($x:expr, $y:expr, $bound:expr) => { 19 | assert_eq_within_bound!($x.x, $y.x, $bound); 20 | assert_eq_within_bound!($x.y, $y.y, $bound); 21 | }; 22 | } 23 | 24 | #[cfg(test)] 25 | macro_rules! assert_eq_point2 { 26 | ($x:expr, $y:expr, $bound:expr) => { 27 | assert_eq_within_bound!($x.x, $y.x, $bound); 28 | assert_eq_within_bound!($x.y, $y.y, $bound); 29 | }; 30 | } 31 | 32 | #[cfg(test)] 33 | macro_rules! assert_eq_vector3 { 34 | ($x:expr, $y:expr, $bound:expr) => { 35 | assert_eq_within_bound!($x.x, $y.x, $bound); 36 | assert_eq_within_bound!($x.y, $y.y, $bound); 37 | assert_eq_within_bound!($x.z, $y.z, $bound); 38 | }; 39 | } 40 | 41 | #[cfg(test)] 42 | macro_rules! assert_eq_point3 { 43 | ($x:expr, $y:expr, $bound:expr) => { 44 | assert_eq_within_bound!($x.x, $y.x, $bound); 45 | assert_eq_within_bound!($x.y, $y.y, $bound); 46 | assert_eq_within_bound!($x.z, $y.z, $bound); 47 | }; 48 | } 49 | 50 | mod complex; 51 | mod matrix4; 52 | mod three_dimensions; 53 | mod transform; 54 | mod two_dimensions; 55 | 56 | pub use self::complex::Complex; 57 | pub use self::matrix4::Matrix4; 58 | pub use self::three_dimensions::{Point3, Vector3}; 59 | pub use self::transform::Transform; 60 | pub use self::two_dimensions::Point2; 61 | -------------------------------------------------------------------------------- /src/math/three_dimensions.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, Mul, Neg, Sub}; 2 | 3 | use serde::Deserialize; 4 | 5 | use super::Matrix4; 6 | 7 | macro_rules! define_struct { 8 | ($T:ident) => { 9 | #[derive(Debug, Copy, Clone, Deserialize)] 10 | pub struct $T { 11 | pub x: f32, 12 | pub y: f32, 13 | pub z: f32, 14 | } 15 | }; 16 | } 17 | 18 | define_struct!(Vector3); 19 | define_struct!(Point3); 20 | 21 | macro_rules! define_impl { 22 | ($T:ident) => { 23 | impl $T { 24 | pub const fn new(x: f32, y: f32, z: f32) -> $T { 25 | $T { x, y, z } 26 | } 27 | 28 | pub fn fuzzy_equal(&self, other: &Point3) -> bool { 29 | self.x >= other.x - 1e-3 30 | && self.x <= other.x + 1e-3 31 | && self.y >= other.y - 1e-3 32 | && self.y <= other.y + 1e-3 33 | && self.z >= other.z - 1e-3 34 | && self.z <= other.z + 1e-3 35 | } 36 | } 37 | }; 38 | } 39 | 40 | define_impl!(Vector3); 41 | define_impl!(Point3); 42 | 43 | macro_rules! define_from { 44 | ($T:ident) => { 45 | impl From<[f32; 3]> for $T { 46 | fn from(values: [f32; 3]) -> $T { 47 | $T::new(values[0], values[1], values[2]) 48 | } 49 | } 50 | 51 | impl From> for $T { 52 | fn from(values: Vec) -> $T { 53 | assert!(values.len() == 3, "Invalid value for from"); 54 | $T::new(values[0], values[1], values[2]) 55 | } 56 | } 57 | }; 58 | } 59 | 60 | define_from!(Vector3); 61 | define_from!(Point3); 62 | 63 | // Vector 3 specific 64 | impl Vector3 { 65 | pub fn dot(&self, other: &Self) -> f32 { 66 | self.x * other.x + self.y * other.y + self.z * other.z 67 | } 68 | 69 | pub fn cross(&self, other: &Self) -> Self { 70 | let x0 = self.y * other.z - self.z * other.y; 71 | let y0 = self.z * other.x - self.x * other.z; 72 | let z0 = self.x * other.y - self.y * other.x; 73 | 74 | Vector3 { 75 | x: x0, 76 | y: y0, 77 | z: z0, 78 | } 79 | } 80 | 81 | pub fn reflect(&self, normal: &Self) -> Self { 82 | *self - *normal * (2.0 * self.dot(normal)) 83 | } 84 | 85 | pub fn length(&self) -> f32 { 86 | self.dot(self).sqrt() 87 | } 88 | 89 | pub fn normalize(&self) -> Self { 90 | let l = self.length(); 91 | 92 | if l == 0.0 { 93 | return Vector3 { 94 | x: 0.0, 95 | y: 0.0, 96 | z: 0.0, 97 | }; 98 | } 99 | 100 | Vector3 { 101 | x: self.x / l, 102 | y: self.y / l, 103 | z: self.z / l, 104 | } 105 | } 106 | 107 | pub fn as_point(&self) -> Point3 { 108 | Point3::new(self.x, self.y, self.z) 109 | } 110 | } 111 | 112 | // Point3 specific 113 | impl Point3 { 114 | pub fn at_origin() -> Point3 { 115 | Point3::new(0.0, 0.0, 0.0) 116 | } 117 | 118 | pub fn as_vector(&self) -> Vector3 { 119 | Vector3::new(self.x, self.y, self.z) 120 | } 121 | } 122 | 123 | // Operators 124 | macro_rules! define_scalar_add { 125 | ($T:ty) => { 126 | impl Add<$T> for Vector3 { 127 | type Output = Vector3; 128 | 129 | fn add(self, other: $T) -> Vector3 { 130 | Vector3 { 131 | x: self.x + f32::from(other), 132 | y: self.y + f32::from(other), 133 | z: self.z + f32::from(other), 134 | } 135 | } 136 | } 137 | }; 138 | } 139 | 140 | macro_rules! define_add { 141 | ($T:ident, $V:ident, $U:ident) => { 142 | impl Add<$V> for $T { 143 | type Output = $U; 144 | 145 | fn add(self, other: $V) -> $U { 146 | $U { 147 | x: self.x + other.x, 148 | y: self.y + other.y, 149 | z: self.z + other.z, 150 | } 151 | } 152 | } 153 | }; 154 | } 155 | 156 | define_add!(Vector3, Vector3, Vector3); 157 | define_add!(Point3, Vector3, Vector3); 158 | 159 | define_scalar_add!(f32); 160 | define_scalar_add!(i8); 161 | define_scalar_add!(i16); 162 | define_scalar_add!(u8); 163 | define_scalar_add!(u16); 164 | 165 | macro_rules! define_scalar_sub { 166 | ($T:ty) => { 167 | impl Sub<$T> for Vector3 { 168 | type Output = Vector3; 169 | 170 | fn sub(self, other: $T) -> Vector3 { 171 | Vector3 { 172 | x: self.x - f32::from(other), 173 | y: self.y - f32::from(other), 174 | z: self.z - f32::from(other), 175 | } 176 | } 177 | } 178 | }; 179 | } 180 | 181 | macro_rules! define_sub { 182 | ($T:ident, $V:ident, $U:ident) => { 183 | impl Sub<$V> for $T { 184 | type Output = $U; 185 | 186 | fn sub(self, other: $V) -> $U { 187 | $U { 188 | x: self.x - other.x, 189 | y: self.y - other.y, 190 | z: self.z - other.z, 191 | } 192 | } 193 | } 194 | }; 195 | } 196 | 197 | define_sub!(Point3, Point3, Vector3); 198 | define_sub!(Vector3, Vector3, Vector3); 199 | 200 | define_scalar_sub!(f32); 201 | define_scalar_sub!(i8); 202 | define_scalar_sub!(i16); 203 | define_scalar_sub!(u8); 204 | define_scalar_sub!(u16); 205 | 206 | impl Neg for Vector3 { 207 | type Output = Vector3; 208 | 209 | fn neg(self) -> Vector3 { 210 | Vector3 { 211 | x: -self.x, 212 | y: -self.y, 213 | z: -self.z, 214 | } 215 | } 216 | } 217 | 218 | macro_rules! define_scalar_mul { 219 | ($T:ty) => { 220 | impl Mul<$T> for Vector3 { 221 | type Output = Vector3; 222 | 223 | fn mul(self, other: $T) -> Vector3 { 224 | Vector3 { 225 | x: self.x * f32::from(other), 226 | y: self.y * f32::from(other), 227 | z: self.z * f32::from(other), 228 | } 229 | } 230 | } 231 | }; 232 | } 233 | 234 | impl Mul for Vector3 { 235 | type Output = Vector3; 236 | 237 | fn mul(self, other: Vector3) -> Vector3 { 238 | Vector3 { 239 | x: self.x * other.x, 240 | y: self.y * other.y, 241 | z: self.z * other.z, 242 | } 243 | } 244 | } 245 | 246 | define_scalar_mul!(f32); 247 | define_scalar_mul!(i8); 248 | define_scalar_mul!(i16); 249 | define_scalar_mul!(u8); 250 | define_scalar_mul!(u16); 251 | 252 | impl Mul for Matrix4 { 253 | type Output = Vector3; 254 | 255 | fn mul(self, other: Vector3) -> Vector3 { 256 | Vector3::new( 257 | self[(0, 0)] * other.x + self[(1, 0)] * other.y + self[(2, 0)] * other.z, 258 | self[(0, 1)] * other.x + self[(1, 1)] * other.y + self[(2, 1)] * other.z, 259 | self[(0, 2)] * other.x + self[(1, 2)] * other.y + self[(2, 2)] * other.z, 260 | ) 261 | } 262 | } 263 | 264 | impl Mul for Matrix4 { 265 | type Output = Point3; 266 | 267 | fn mul(self, other: Point3) -> Point3 { 268 | let mut x = 269 | self[(0, 0)] * other.x + self[(1, 0)] * other.y + self[(2, 0)] * other.z + self[(3, 0)]; 270 | let mut y = 271 | self[(0, 1)] * other.x + self[(1, 1)] * other.y + self[(2, 1)] * other.z + self[(3, 1)]; 272 | let mut z = 273 | self[(0, 2)] * other.x + self[(1, 2)] * other.y + self[(2, 2)] * other.z + self[(3, 2)]; 274 | let w = 275 | self[(0, 3)] * other.x + self[(1, 3)] * other.y + self[(2, 3)] * other.z + self[(3, 3)]; 276 | 277 | if !(w > (0.0 - 1e-3) && w < (0.0 + 1e-3) || w > (1.0 - 1e-3) && w < (1.0 + 1e-3)) { 278 | // assert!(false, "Bad value for w {}", w); 279 | x /= w; 280 | y /= w; 281 | z /= w; 282 | } 283 | 284 | Point3::new(x, y, z) 285 | } 286 | } 287 | #[cfg(test)] 288 | mod tests { 289 | use super::*; 290 | use std::f32::consts::PI; 291 | const EPSILON: f32 = 1e-3; 292 | 293 | #[test] 294 | fn test_constructor() { 295 | let vec = Vector3::new(2.0, 1.0, 0.0); 296 | 297 | assert_eq_within_bound!(vec.x, 2.0, EPSILON); 298 | assert_eq_within_bound!(vec.y, 1.0, EPSILON); 299 | assert_eq_within_bound!(vec.z, 0.0, EPSILON); 300 | } 301 | 302 | #[test] 303 | fn test_at_origin() { 304 | let vec = Point3::at_origin(); 305 | 306 | assert_eq_point3!( 307 | vec, 308 | Point3 { 309 | x: 0.0, 310 | y: 0.0, 311 | z: 0.0, 312 | }, 313 | EPSILON 314 | ); 315 | } 316 | 317 | #[test] 318 | fn test_dot() { 319 | let vec1 = Vector3::new(3.52, 8.23, 29.0); 320 | 321 | let vec2 = Vector3::new(0.0, 1.3, -3.23); 322 | 323 | assert_eq_within_bound!(vec1.dot(&vec2), -82.971, EPSILON); 324 | assert_eq_within_bound!(vec2.dot(&vec1), -82.971, EPSILON); 325 | } 326 | 327 | #[test] 328 | fn test_cross() { 329 | let vec1 = Vector3::new(2.4, 9.3, -1.3); 330 | 331 | let vec2 = Vector3::new(-2.3, 2.5, -3.5); 332 | 333 | let result1 = vec1.cross(&vec2); 334 | let result2 = vec2.cross(&vec1); 335 | 336 | assert_eq_vector3!(result1, Vector3::new(-29.3, 11.39, 27.39,), EPSILON); 337 | assert_eq_vector3!(result2, Vector3::new(29.3, -11.39, -27.39,), EPSILON); 338 | } 339 | 340 | #[test] 341 | fn test_length() { 342 | let vectors = vec![ 343 | (Vector3::new(0.0, 0.0, 0.0), 0.0), 344 | (Vector3::new(2.3, -2.1, 2.1), 3.756_328), 345 | (Vector3::new(1.0, 0.0, 0.0), 1.0), 346 | (Vector3::new(0.80181, 0.26921, 0.53351), 1.0), 347 | ]; 348 | 349 | for (vec, length) in vectors { 350 | assert_eq_within_bound!(vec.length(), length, EPSILON); 351 | } 352 | } 353 | 354 | #[test] 355 | fn test_normalize_zero_length() { 356 | let vec = Vector3::new(0.0, 0.0, 0.0); 357 | 358 | assert_eq_vector3!(vec.normalize(), vec, EPSILON); 359 | } 360 | 361 | #[test] 362 | fn test_normalize() { 363 | let vec = Vector3::new(4.0, 63.0, 0.5); 364 | 365 | let result = vec.normalize(); 366 | let expected = Vector3::new(0.063_362_49, 0.99795915, 0.007920311); 367 | 368 | assert_eq_vector3!(result, expected, EPSILON); 369 | assert_eq_within_bound!(result.length(), 1.0, EPSILON); 370 | } 371 | 372 | #[test] 373 | fn test_addition() { 374 | let vec1 = Vector3::new(1.0, 5.0, 3.0); 375 | 376 | let vec2 = Vector3::new(3.2, 3.1, 2.1); 377 | 378 | let expected1 = Vector3::new(4.2, 8.1, 5.1); 379 | 380 | let expected2 = Vector3::new(11.0, 15.0, 13.0); 381 | 382 | assert_eq_vector3!(vec1 + vec2, expected1, EPSILON); 383 | 384 | let result: Vector3 = vec1 + 10u16; 385 | assert_eq_vector3!(result, expected2, EPSILON); 386 | } 387 | 388 | #[test] 389 | fn test_subtraction() { 390 | let vec1 = Vector3::new(1.0, 5.0, 3.0); 391 | 392 | let vec2 = Vector3::new(3.2, 3.1, 2.1); 393 | 394 | let expected1 = Vector3::new(-2.2, 1.9, 0.9); 395 | 396 | let expected2 = Vector3::new(-19.0, -15.0, -17.0); 397 | 398 | assert_eq_vector3!(vec1 - vec2, expected1, EPSILON); 399 | 400 | let result: Vector3 = vec1 - 20.0; 401 | assert_eq_vector3!(result, expected2, EPSILON); 402 | } 403 | 404 | #[test] 405 | fn test_multiplication() { 406 | let vec1 = Vector3::new(1.0, 5.0, 3.0); 407 | 408 | let vec2 = Vector3::new(3.2, 3.1, 2.1); 409 | 410 | let expected1 = Vector3::new(3.2, 15.5, 6.3); 411 | let expected2 = Vector3::new(20.0, 100.0, 60.0); 412 | 413 | assert_eq_vector3!(vec1 * vec2, expected1, EPSILON); 414 | 415 | let result: Vector3 = vec1 * 20.0; 416 | assert_eq_vector3!(result, expected2, EPSILON); 417 | } 418 | 419 | #[test] 420 | fn test_vector3_mul_simple() { 421 | let m = Matrix4::identity(); 422 | 423 | let result = m * Vector3::new(2.4, 3.1, 9.0); 424 | 425 | assert_eq_vector3!(result, Vector3::new(2.4, 3.1, 9.0), EPSILON); 426 | } 427 | 428 | #[test] 429 | fn test_vector3_mul_complex() { 430 | let m = Matrix4::new([ 431 | [15.0, 1.3, -2.8, 0.0], 432 | [-1.4, 7.8, 3.5, 0.0], 433 | [5.0, -3.6, 1.0, 0.0], 434 | [12.3, 9.1, -1.2, 1.0], 435 | ]); 436 | 437 | let result = m * Vector3::new(2.4, 3.2, -1.0); 438 | 439 | assert_eq_vector3!(result, Vector3::new(26.52, 31.68, 3.48), EPSILON); 440 | } 441 | 442 | #[test] 443 | fn test_translation() { 444 | let v = Point3::new(1.5, 9.9, -5.6); 445 | let m = Matrix4::translate(-2.0, 3.0, 5.0); 446 | 447 | let expected = Point3::new(&v.x - 2.0, &v.y + 3.0, &v.z + 5.0); 448 | 449 | assert_eq_point3!(m * v, expected, EPSILON); 450 | } 451 | 452 | #[test] 453 | fn test_vector_translation() { 454 | let v = Vector3::new(0.0, 0.0, -1.0); 455 | let m = Matrix4::translate(10.0, 0.0, 0.0); 456 | 457 | let expected = Point3::new(0.0, 0.0, -1.0); 458 | 459 | assert_eq_point3!(m * v, expected, EPSILON); 460 | } 461 | 462 | #[test] 463 | fn test_scale() { 464 | let v = Vector3::new(1.0, 1.0, 1.0); 465 | let m = Matrix4::scale(-2.0, 3.0, 5.0); 466 | 467 | let expected = Vector3::new(v.x * -2.0, v.y * 3.0, v.z * 5.0); 468 | 469 | assert_eq_vector3!(m * v, expected, EPSILON); 470 | } 471 | 472 | #[test] 473 | fn test_rot_x() { 474 | let v = Vector3::new(1.0, 0.0, 1.0); 475 | let m = Matrix4::rot_x(PI / 2.0); 476 | 477 | assert_eq_vector3!(Vector3::new(1.0, -1.0, 0.0), m * v, EPSILON); 478 | } 479 | 480 | #[test] 481 | fn test_rot_y() { 482 | let v = Vector3::new(0.0, 1.0, 1.0); 483 | let m = Matrix4::rot_y(PI / 2.0); 484 | 485 | assert_eq_vector3!(Vector3::new(1.0, 1.0, 0.0), m * v, EPSILON); 486 | } 487 | 488 | #[test] 489 | fn test_rot_z() { 490 | let v = Vector3::new(1.0, 0.0, 1.0); 491 | let m = Matrix4::rot_z(PI / 2.0); 492 | 493 | assert_eq_vector3!(Vector3::new(0.0, 1.0, 1.0), m * v, EPSILON); 494 | } 495 | 496 | #[test] 497 | fn test_from() { 498 | let v = Vector3::new(1.0, 2.0, 3.0); 499 | let p = Point3::new(1.0, 2.0, 3.0); 500 | 501 | assert_eq_vector3!(Vector3::from([1.0, 2.0, 3.0]), v, EPSILON); 502 | assert_eq_vector3!(Vector3::from(vec![1.0, 2.0, 3.0]), v, EPSILON); 503 | 504 | assert_eq_point3!(Point3::from([1.0, 2.0, 3.0]), p, EPSILON); 505 | assert_eq_point3!(Point3::from(vec![1.0, 2.0, 3.0]), p, EPSILON); 506 | } 507 | } 508 | -------------------------------------------------------------------------------- /src/math/transform.rs: -------------------------------------------------------------------------------- 1 | use super::Matrix4; 2 | 3 | pub struct Transform { 4 | pub matrix: Matrix4, 5 | pub normal_matrix: Matrix4, 6 | } 7 | 8 | impl Transform { 9 | pub fn new(matrix: Matrix4) -> Self { 10 | Self { 11 | matrix, 12 | normal_matrix: matrix.transpose(), 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/math/two_dimensions.rs: -------------------------------------------------------------------------------- 1 | use std::ops::{Add, Mul, Neg, Sub}; 2 | 3 | use serde::Deserialize; 4 | 5 | macro_rules! define_struct { 6 | ($T:ident) => { 7 | #[derive(Debug, Copy, Clone, Deserialize)] 8 | pub struct $T { 9 | pub x: f32, 10 | pub y: f32, 11 | } 12 | }; 13 | } 14 | 15 | define_struct!(Vector2); 16 | define_struct!(Point2); 17 | 18 | macro_rules! define_impl { 19 | ($T:ident) => { 20 | impl $T { 21 | pub fn new(x: f32, y: f32) -> $T { 22 | $T { x, y } 23 | } 24 | } 25 | }; 26 | } 27 | 28 | define_impl!(Vector2); 29 | define_impl!(Point2); 30 | 31 | macro_rules! define_from { 32 | ($T:ident) => { 33 | impl From<[f32; 2]> for $T { 34 | fn from(values: [f32; 2]) -> $T { 35 | $T::new(values[0], values[1]) 36 | } 37 | } 38 | 39 | impl From> for $T { 40 | fn from(values: Vec) -> $T { 41 | assert!(values.len() == 2, "Invalid value for from"); 42 | $T::new(values[0], values[1]) 43 | } 44 | } 45 | }; 46 | } 47 | 48 | define_from!(Vector2); 49 | define_from!(Point2); 50 | 51 | // Vector 3 specific 52 | impl Vector2 { 53 | pub fn dot(self, other: &Self) -> f32 { 54 | self.x * other.x + self.y * other.y 55 | } 56 | 57 | pub fn length(self) -> f32 { 58 | self.dot(&self).sqrt() 59 | } 60 | 61 | pub fn normalize(self) -> Self { 62 | let l = self.length(); 63 | 64 | if l == 0.0 { 65 | return Vector2 { x: 0.0, y: 0.0 }; 66 | } 67 | 68 | Vector2 { 69 | x: self.x / l, 70 | y: self.y / l, 71 | } 72 | } 73 | 74 | pub fn as_point(self) -> Point2 { 75 | Point2::new(self.x, self.y) 76 | } 77 | } 78 | 79 | // Point2 specific 80 | impl Point2 { 81 | pub fn at_origin() -> Point2 { 82 | Point2::new(0.0, 0.0) 83 | } 84 | 85 | pub fn as_vector(&self) -> Vector2 { 86 | Vector2::new(self.x, self.y) 87 | } 88 | } 89 | 90 | // Operators 91 | 92 | macro_rules! define_scalar_add { 93 | ($T:ty) => { 94 | impl Add<$T> for Vector2 { 95 | type Output = Vector2; 96 | 97 | fn add(self, other: $T) -> Vector2 { 98 | Vector2 { 99 | x: self.x + f32::from(other), 100 | y: self.y + f32::from(other), 101 | } 102 | } 103 | } 104 | }; 105 | } 106 | 107 | macro_rules! define_add { 108 | ($T:ident, $V:ident, $U:ident) => { 109 | impl Add<$V> for $T { 110 | type Output = $U; 111 | 112 | fn add(self, other: $V) -> $U { 113 | $U { 114 | x: self.x + other.x, 115 | y: self.y + other.y, 116 | } 117 | } 118 | } 119 | }; 120 | } 121 | 122 | define_add!(Vector2, Vector2, Vector2); 123 | define_add!(Point2, Vector2, Vector2); 124 | 125 | define_scalar_add!(f32); 126 | define_scalar_add!(i8); 127 | define_scalar_add!(i16); 128 | define_scalar_add!(u8); 129 | define_scalar_add!(u16); 130 | 131 | macro_rules! define_scalar_sub { 132 | ($T:ty) => { 133 | impl Sub<$T> for Vector2 { 134 | type Output = Vector2; 135 | 136 | fn sub(self, other: $T) -> Vector2 { 137 | Vector2 { 138 | x: self.x - f32::from(other), 139 | y: self.y - f32::from(other), 140 | } 141 | } 142 | } 143 | }; 144 | } 145 | 146 | macro_rules! define_sub { 147 | ($T:ident, $V:ident, $U:ident) => { 148 | impl Sub<$V> for $T { 149 | type Output = $U; 150 | 151 | fn sub(self, other: $V) -> $U { 152 | $U { 153 | x: self.x - other.x, 154 | y: self.y - other.y, 155 | } 156 | } 157 | } 158 | }; 159 | } 160 | 161 | define_sub!(Point2, Point2, Vector2); 162 | define_sub!(Vector2, Vector2, Vector2); 163 | 164 | define_scalar_sub!(f32); 165 | define_scalar_sub!(i8); 166 | define_scalar_sub!(i16); 167 | define_scalar_sub!(u8); 168 | define_scalar_sub!(u16); 169 | 170 | macro_rules! define_scalar_mul { 171 | ($T:ty) => { 172 | impl Mul<$T> for Vector2 { 173 | type Output = Vector2; 174 | 175 | fn mul(self, other: $T) -> Vector2 { 176 | Vector2 { 177 | x: self.x * f32::from(other), 178 | y: self.y * f32::from(other), 179 | } 180 | } 181 | } 182 | }; 183 | } 184 | 185 | impl Mul for Vector2 { 186 | type Output = Vector2; 187 | 188 | fn mul(self, other: Vector2) -> Vector2 { 189 | Vector2 { 190 | x: self.x * other.x, 191 | y: self.y * other.y, 192 | } 193 | } 194 | } 195 | 196 | define_scalar_mul!(f32); 197 | define_scalar_mul!(i8); 198 | define_scalar_mul!(i16); 199 | define_scalar_mul!(u8); 200 | define_scalar_mul!(u16); 201 | 202 | impl Neg for Vector2 { 203 | type Output = Vector2; 204 | 205 | fn neg(self) -> Vector2 { 206 | Vector2 { 207 | x: -self.x, 208 | y: -self.y, 209 | } 210 | } 211 | } 212 | 213 | #[cfg(test)] 214 | mod tests { 215 | use super::super::EPSILON; 216 | use super::*; 217 | 218 | #[test] 219 | fn test_new() { 220 | let v = Vector2::new(2.0, 1.0); 221 | let p = Point2::new(2.0, 1.0); 222 | 223 | assert_eq_within_bound!(v.x, 2.0, EPSILON); 224 | assert_eq_within_bound!(v.y, 1.0, EPSILON); 225 | 226 | assert_eq_within_bound!(p.x, 2.0, EPSILON); 227 | assert_eq_within_bound!(p.y, 1.0, EPSILON); 228 | } 229 | 230 | // Vector2 specifics 231 | #[test] 232 | fn test_vector2_as_point() { 233 | let v = Vector2::new(3.0, 1.5); 234 | 235 | assert_eq_point2!(v.as_point(), Point2 { x: 3.0, y: 1.5 }, EPSILON); 236 | } 237 | 238 | #[test] 239 | fn test_vector2_length() { 240 | let v = Vector2::new(29.2, 12.0); 241 | 242 | assert_eq_within_bound!(v.length(), 31.569_605, EPSILON); 243 | } 244 | 245 | #[test] 246 | fn test_vector2_dot() { 247 | let v1 = Vector2::new(29.2, 12.0); 248 | let v2 = Vector2::new(2.5, 0.0); 249 | 250 | assert_eq_within_bound!(v1.dot(&v2), 73.0, EPSILON); 251 | } 252 | 253 | #[test] 254 | fn test_vector2_normalize() { 255 | let v1 = Vector2::new(29.2, 12.0); 256 | 257 | let normalized = v1.normalize(); 258 | 259 | assert_eq_within_bound!(normalized.length(), 1.0, EPSILON); 260 | assert_eq_vector2!( 261 | normalized, 262 | Vector2 { 263 | x: 0.924_940_3, 264 | y: 0.380_112_44 265 | }, 266 | EPSILON 267 | ); 268 | } 269 | 270 | #[test] 271 | fn test_vector2_addition() { 272 | let v1 = Vector2::new(2.0, 3.0); 273 | let v2 = Vector2::new(1.5, 9.0); 274 | 275 | assert_eq_vector2!(v1 + v2, Vector2 { x: 3.5, y: 12.0 }, EPSILON); 276 | assert_eq_vector2!(v1 + 10u16, Vector2 { x: 12.0, y: 13.0 }, EPSILON); 277 | } 278 | 279 | #[test] 280 | fn test_vector2_substraction() { 281 | let v1 = Vector2::new(2.0, 3.0); 282 | let v2 = Vector2::new(1.5, 9.0); 283 | 284 | assert_eq_vector2!(v1 - v2, Vector2 { x: 0.5, y: -6.0 }, EPSILON); 285 | assert_eq_vector2!(v1 - 10u16, Vector2 { x: -8.0, y: -7.0 }, EPSILON); 286 | } 287 | 288 | #[test] 289 | fn test_vector2_multiplication() { 290 | let v1 = Vector2::new(2.0, 3.0); 291 | let v2 = Vector2::new(1.5, 9.0); 292 | 293 | assert_eq_vector2!(v1 * v2, Vector2 { x: 3.0, y: 27.0 }, EPSILON); 294 | assert_eq_vector2!(v1 * 3.0, Vector2 { x: 6.0, y: 9.0 }, EPSILON); 295 | } 296 | 297 | #[test] 298 | fn test_vector2_negation() { 299 | let v = Vector2::new(29.2, 12.0); 300 | 301 | assert_eq_vector2!(-v, Vector2 { x: -29.2, y: -12.0 }, EPSILON); 302 | } 303 | 304 | // Point2 specifics 305 | #[test] 306 | fn test_point2_at_origin() { 307 | let p = Point2::at_origin(); 308 | 309 | assert_eq_point2!(p, Point2 { x: 0.0, y: 0.0 }, EPSILON); 310 | } 311 | 312 | #[test] 313 | fn test_point2_as_vector() { 314 | let p = Point2::new(3.0, 1.5); 315 | 316 | assert_eq_vector2!(p.as_vector(), Vector2 { x: 3.0, y: 1.5 }, EPSILON); 317 | } 318 | 319 | #[test] 320 | fn test_point2_addition() { 321 | let p = Point2::new(3.0, 1.5); 322 | let v = Vector2::new(2.0, 3.0); 323 | 324 | assert_eq_vector2!(p + v, Vector2 { x: 5.0, y: 4.5 }, EPSILON); 325 | } 326 | 327 | #[test] 328 | fn test_from() { 329 | let p = Point2::new(3.0, 1.5); 330 | let v = Vector2::new(2.0, 3.0); 331 | 332 | assert_eq_vector2!(Vector2::from([2.0, 3.0]), v, EPSILON); 333 | assert_eq_vector2!(Vector2::from(vec![2.0, 3.0]), v, EPSILON); 334 | 335 | assert_eq_point2!(Point2::from([3.0, 1.5]), p, EPSILON); 336 | assert_eq_point2!(Point2::from(vec![3.0, 1.5]), p, EPSILON); 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /src/mesh_loader.rs: -------------------------------------------------------------------------------- 1 | extern crate tobj; 2 | 3 | use std::collections::HashMap; 4 | use std::convert::TryFrom; 5 | use std::error::Error; 6 | use std::fmt; 7 | use std::path::{Path, PathBuf}; 8 | use std::rc::Rc; 9 | 10 | use tracing::{debug, info, warn}; 11 | 12 | use crate::color::Color; 13 | use crate::geometry::triangle::Normal; 14 | use crate::geometry::{BoundingVolume, Instance, Mesh, Triangle, TriangleStorage}; 15 | use crate::material::{ 16 | IllumninationModel, IllumninationModelParsingError, Material, OptionalTexture, 17 | }; 18 | use crate::math::{Point3, Vector3}; 19 | use crate::texture; 20 | 21 | #[derive(Debug)] 22 | pub enum MeshLoadError { 23 | TextureLoadError(texture::file::FileError), 24 | IllumenationModelParsingError(IllumninationModelParsingError), 25 | } 26 | 27 | impl From for MeshLoadError { 28 | fn from(texture_error: texture::file::FileError) -> Self { 29 | MeshLoadError::TextureLoadError(texture_error) 30 | } 31 | } 32 | 33 | impl From for MeshLoadError { 34 | fn from(illumination_model_error: IllumninationModelParsingError) -> Self { 35 | MeshLoadError::IllumenationModelParsingError(illumination_model_error) 36 | } 37 | } 38 | 39 | impl fmt::Display for MeshLoadError { 40 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 41 | match self { 42 | MeshLoadError::TextureLoadError(inner_error) => write!( 43 | f, 44 | "Failed to load meshes with texture load error: {}", 45 | inner_error 46 | ), 47 | MeshLoadError::IllumenationModelParsingError(inner_error) => write!( 48 | f, 49 | "Failed to load meshes with illumination model error: {}", 50 | inner_error 51 | ), 52 | } 53 | } 54 | } 55 | 56 | impl Error for MeshLoadError { 57 | fn source(&self) -> Option<&(dyn Error + 'static)> { 58 | match self { 59 | MeshLoadError::TextureLoadError(inner_error) => Some(inner_error), 60 | MeshLoadError::IllumenationModelParsingError(inner_error) => Some(inner_error), 61 | } 62 | } 63 | } 64 | 65 | type LoadedObj = Rc<(Vec, Vec)>; 66 | 67 | pub struct MeshLoader { 68 | root_path: PathBuf, 69 | mesh_cache: HashMap>>, 70 | obj_cache: HashMap, 71 | } 72 | 73 | impl<'a, V: BoundingVolume, S: 'a + TriangleStorage<'a>> MeshLoader { 74 | pub fn new(root_path: PathBuf) -> MeshLoader { 75 | MeshLoader { 76 | root_path, 77 | mesh_cache: HashMap::default(), 78 | obj_cache: HashMap::default(), 79 | } 80 | } 81 | 82 | pub fn load( 83 | &mut self, 84 | path: &Path, 85 | fallback_material: Rc, 86 | ) -> Result>>, MeshLoadError> { 87 | let final_path = self.root_path.join(path); 88 | let obj = self.load_obj(&final_path); 89 | let (models, materials) = obj.as_ref(); 90 | let mut meshes = vec![]; 91 | let material_cache = self.build_material_cache(&final_path, materials)?; 92 | 93 | for m in models.iter() { 94 | if let Some(mesh) = self.prepare_mesh(m, &material_cache, &fallback_material) { 95 | meshes.push(Box::new(mesh)); 96 | } 97 | } 98 | 99 | Ok(meshes) 100 | } 101 | 102 | pub fn load_instance( 103 | &mut self, 104 | path: &Path, 105 | fallback_material: Rc, 106 | ) -> Result>>, MeshLoadError> { 107 | let final_path = self.root_path.join(path); 108 | let obj = self.load_obj(&final_path); 109 | let (models, materials) = obj.as_ref(); 110 | let mut meshes = vec![]; 111 | let material_cache = self.build_material_cache(&final_path, materials)?; 112 | 113 | for m in models.iter() { 114 | let cache_key = Self::build_cache_key(&final_path.to_string_lossy(), &m.name); 115 | 116 | if let Some(mesh) = self.mesh_cache.get(&cache_key) { 117 | meshes.push(Box::new(Instance::new( 118 | Rc::clone(mesh), 119 | fallback_material.clone(), 120 | ))); 121 | continue; 122 | } 123 | 124 | if let Some(mut mesh) = self.prepare_mesh(m, &material_cache, &fallback_material) { 125 | // Make sure any acceleration structures are built 126 | // in model space. 127 | mesh.rebuild_accelleration_structure(); 128 | 129 | let mesh = Rc::new(mesh); 130 | let instance = Box::new(Instance::new(Rc::clone(&mesh), fallback_material.clone())); 131 | self.mesh_cache 132 | .insert(cache_key.to_owned(), Rc::clone(&mesh)); 133 | 134 | meshes.push(instance); 135 | } 136 | } 137 | 138 | Ok(meshes) 139 | } 140 | 141 | fn prepare_mesh( 142 | &self, 143 | model: &tobj::Model, 144 | material_cache: &HashMap>, 145 | fallback_material: &Rc, 146 | ) -> Option> { 147 | let mesh = &model.mesh; 148 | if mesh.indices.is_empty() && mesh.positions.is_empty() { 149 | return None; 150 | } 151 | 152 | let mut triangles = Vec::with_capacity(mesh.indices.len() / 3); 153 | info!("Mesh name {}", model.name); 154 | debug!("Num indices: {}", mesh.indices.len()); 155 | debug!("Num vertices: {}", mesh.positions.len()); 156 | debug!("Num normals: {}", mesh.normals.len()); 157 | debug!("Num texture coords: {}", mesh.texcoords.len()); 158 | let use_vertex_normals = !mesh.normals.is_empty(); 159 | let has_texture_coords = !mesh.texcoords.is_empty(); 160 | 161 | if use_vertex_normals { 162 | debug!("Using vertex normals"); 163 | } else { 164 | debug!("Using face normals") 165 | } 166 | 167 | if has_texture_coords { 168 | debug!("Using textures"); 169 | } 170 | 171 | for f in 0..mesh.indices.len() / 3 { 172 | let i0 = mesh.indices[f * 3] as usize; 173 | let i1 = mesh.indices[f * 3 + 1] as usize; 174 | let i2 = mesh.indices[f * 3 + 2] as usize; 175 | 176 | let p0 = Point3::new( 177 | mesh.positions[i0 * 3], 178 | mesh.positions[i0 * 3 + 1], 179 | mesh.positions[i0 * 3 + 2], 180 | ); 181 | let p1 = Point3::new( 182 | mesh.positions[i1 * 3], 183 | mesh.positions[i1 * 3 + 1], 184 | mesh.positions[i1 * 3 + 2], 185 | ); 186 | let p2 = Point3::new( 187 | mesh.positions[i2 * 3], 188 | mesh.positions[i2 * 3 + 1], 189 | mesh.positions[i2 * 3 + 2], 190 | ); 191 | 192 | let normal = if use_vertex_normals { 193 | let n0 = Vector3::new( 194 | mesh.normals[i0 * 3], 195 | mesh.normals[i0 * 3 + 1], 196 | mesh.normals[i0 * 3 + 2], 197 | ); 198 | let n1 = Vector3::new( 199 | mesh.normals[i1 * 3], 200 | mesh.normals[i1 * 3 + 1], 201 | mesh.normals[i1 * 3 + 2], 202 | ); 203 | let n2 = Vector3::new( 204 | mesh.normals[i2 * 3], 205 | mesh.normals[i2 * 3 + 1], 206 | mesh.normals[i2 * 3 + 2], 207 | ); 208 | 209 | Normal::Vertex(n0, n1, n2) 210 | } else { 211 | let ab = p0 - p1; 212 | let ac = p0 - p2; 213 | 214 | Normal::Face(ab.cross(&ac).normalize()) 215 | }; 216 | 217 | let texture_coords = if has_texture_coords { 218 | Some([ 219 | texture::TextureCoord::new(mesh.texcoords[i0 * 2], mesh.texcoords[i0 * 2 + 1]), 220 | texture::TextureCoord::new(mesh.texcoords[i1 * 2], mesh.texcoords[i1 * 2 + 1]), 221 | texture::TextureCoord::new(mesh.texcoords[i2 * 2], mesh.texcoords[i2 * 2 + 1]), 222 | ]) 223 | } else { 224 | None 225 | }; 226 | 227 | let mut material = fallback_material.clone(); 228 | if let Some(id) = mesh.material_id { 229 | if let Some(m) = material_cache.get(&id) { 230 | material = m.clone(); 231 | } 232 | } 233 | 234 | triangles.push(Triangle::new(p0, p1, p2, normal, texture_coords, material)); 235 | } 236 | 237 | Some(Mesh::new(triangles)) 238 | } 239 | 240 | fn load_obj(&mut self, path: &Path) -> LoadedObj { 241 | Rc::clone( 242 | self.obj_cache 243 | .entry(path.to_string_lossy().to_string()) 244 | .or_insert_with(|| { 245 | let result = tobj::load_obj( 246 | path, 247 | &tobj::LoadOptions { 248 | single_index: false, 249 | triangulate: true, 250 | ignore_points: true, 251 | ignore_lines: true, 252 | }, 253 | ); 254 | // TODO: Better error handling 255 | let (models, materials) = result.unwrap_or_else(|e| { 256 | panic!( 257 | "Failed to load data from {} with error: {}", 258 | path.display(), 259 | e 260 | ) 261 | }); 262 | let materials = materials.unwrap_or_else(|e| { 263 | warn!("Failed to load materials for {:?} with error: {}", path, e); 264 | 265 | Vec::default() 266 | }); 267 | 268 | Rc::new((models, materials)) 269 | }), 270 | ) 271 | } 272 | 273 | fn build_material_cache( 274 | &self, 275 | path: &Path, 276 | materials: &[tobj::Material], 277 | ) -> Result>, MeshLoadError> { 278 | let mut material_cache = HashMap::new(); 279 | for (i, m) in materials.iter().enumerate() { 280 | let illumination_model = match m.illumination_model { 281 | Some(model) => IllumninationModel::try_from(model)?, 282 | None => IllumninationModel::DiffuseSpecular, 283 | }; 284 | 285 | let ambient_texture = 286 | self.load_texture_from_file(path, m.ambient_texture.as_deref())?; 287 | let diffuse_texture = 288 | self.load_texture_from_file(path, m.diffuse_texture.as_deref())?; 289 | let specular_texture = 290 | self.load_texture_from_file(path, m.specular_texture.as_deref())?; 291 | 292 | let ambient = m.ambient.unwrap_or([0.0; 3]); 293 | let diffuse = m.diffuse.unwrap_or([0.0; 3]); 294 | let specular = m.specular.unwrap_or([0.0; 3]); 295 | 296 | let mat = Rc::new(Material::new_with_textures( 297 | Color::new_f32(ambient[0], ambient[1], ambient[2]), 298 | ambient_texture, 299 | Color::new_f32(diffuse[0], diffuse[1], diffuse[2]), 300 | diffuse_texture, 301 | Color::new_f32(specular[0], specular[1], specular[2]), 302 | specular_texture, 303 | m.shininess.unwrap_or(0.0), 304 | illumination_model, 305 | m.shininess.map(|s| 1000.0 / s), 306 | m.optical_density, 307 | )); 308 | 309 | material_cache.insert(i, mat); 310 | } 311 | 312 | Ok(material_cache) 313 | } 314 | 315 | fn load_texture_from_file( 316 | &self, 317 | obj_path: &Path, 318 | texture: Option<&str>, 319 | ) -> Result { 320 | let Some(path) = texture else { return Ok(None) }; 321 | if path.is_empty() { 322 | return Ok(None); 323 | } 324 | 325 | let full_path = if let Some(resolve_path) = obj_path.parent() { 326 | resolve_path.join(path) 327 | } else { 328 | PathBuf::from(path) 329 | }; 330 | let texture = texture::file::File::new(full_path)?; 331 | 332 | Ok(Some(Rc::new(texture))) 333 | } 334 | 335 | fn build_cache_key(filename: &str, mesh_name: &str) -> String { 336 | format!("{}-{}", filename, mesh_name) 337 | } 338 | } 339 | -------------------------------------------------------------------------------- /src/ray.rs: -------------------------------------------------------------------------------- 1 | use crate::math::{Point3, Vector3}; 2 | 3 | #[derive(Debug, Copy, Clone)] 4 | pub struct Ray { 5 | pub origin: Point3, 6 | pub direction: Vector3, 7 | pub inv_direction: Vector3, 8 | pub sign: [usize; 3], 9 | pub medium_refraction: f32, 10 | } 11 | 12 | impl Ray { 13 | pub fn new(origin: Point3, direction: Vector3, medium_refraction: Option) -> Ray { 14 | let inv_dir = Vector3::new(1.0 / direction.x, 1.0 / direction.y, 1.0 / direction.z); 15 | Ray { 16 | origin, 17 | direction, 18 | inv_direction: inv_dir, 19 | sign: [ 20 | (inv_dir.x < 0.0) as usize, 21 | (inv_dir.y < 0.0) as usize, 22 | (inv_dir.z < 0.0) as usize, 23 | ], 24 | medium_refraction: medium_refraction.unwrap_or(1.0), 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/renderer.rs: -------------------------------------------------------------------------------- 1 | use std::ops::Range; 2 | 3 | use rayon::prelude::*; 4 | use serde::Deserialize; 5 | 6 | use crate::camera::Camera; 7 | use crate::color::Color; 8 | use crate::intersection::Intersection; 9 | use crate::material::{IllumninationModel, Material}; 10 | use crate::math::Vector3; 11 | use crate::ray::Ray; 12 | use crate::scene::Scene; 13 | 14 | const RAY_OFFSET: f32 = 1e-3; 15 | const GAMMA: f32 = 1.8; 16 | 17 | #[derive(Deserialize, Debug, Copy, Clone)] 18 | pub enum SuperSampling { 19 | Off, 20 | On(u32), 21 | } 22 | 23 | pub struct Renderer { 24 | scene: Scene, 25 | camera: Camera, 26 | super_sampling: SuperSampling, 27 | } 28 | 29 | pub struct RefractionProperties { 30 | n1: f32, 31 | n2: f32, 32 | n: f32, 33 | cos_i: f32, 34 | c2: f32, 35 | normal: Vector3, 36 | } 37 | 38 | impl RefractionProperties { 39 | fn new(intersection: &Intersection, original_ray: &Ray) -> RefractionProperties { 40 | let refraction_coefficient = if intersection.inside { 41 | 1.0 42 | } else { 43 | intersection 44 | .shape 45 | .material() 46 | .refraction_coefficient 47 | .unwrap_or(1.0) 48 | }; 49 | 50 | let n = original_ray.medium_refraction / refraction_coefficient; 51 | let normal = if intersection.inside { 52 | -intersection.normal 53 | } else { 54 | intersection.normal 55 | }; 56 | 57 | let cos_i = normal.dot(&original_ray.direction); 58 | let c2 = 1.0 - n * n * (1.0 - cos_i * cos_i); 59 | 60 | RefractionProperties { 61 | n1: original_ray.medium_refraction, 62 | n2: refraction_coefficient, 63 | n, 64 | cos_i, 65 | c2, 66 | normal, 67 | } 68 | } 69 | 70 | fn total_internal_reflection(&self) -> bool { 71 | self.c2 < 0.0 72 | } 73 | } 74 | 75 | unsafe impl Sync for Renderer {} 76 | unsafe impl Send for Renderer {} 77 | 78 | impl Renderer { 79 | pub fn new(scene: Scene, camera: Camera, super_sampling: SuperSampling) -> Renderer { 80 | Renderer { 81 | scene, 82 | camera, 83 | super_sampling, 84 | } 85 | } 86 | 87 | pub fn render(&self, max_depth: u32) -> Vec { 88 | let range: Range = 0..(self.camera.height as usize); 89 | let width = self.camera.width as usize; 90 | 91 | range 92 | .into_par_iter() 93 | .flat_map(|y| { 94 | (0..width) 95 | .flat_map(move |x| self.render_point(max_depth, x, y)) 96 | .collect::>() 97 | .into_par_iter() 98 | }) 99 | .collect::>() 100 | } 101 | 102 | fn render_point(&self, max_depth: u32, x: usize, y: usize) -> Color { 103 | let samples = match self.super_sampling { 104 | SuperSampling::Off => 1, 105 | SuperSampling::On(samples) => samples, 106 | }; 107 | 108 | let mut color = [0.0; 3]; 109 | 110 | for x_sample in 0..samples { 111 | for y_sample in 0..samples { 112 | let ray = self.camera.create_ray( 113 | x as u32, 114 | self.camera.height - y as u32, 115 | x_sample, 116 | y_sample, 117 | samples, 118 | ); 119 | let result = self.trace(ray, max_depth, true); 120 | 121 | color[0] += result.r_f32(); 122 | color[1] += result.g_f32(); 123 | color[2] += result.b_f32(); 124 | } 125 | } 126 | 127 | let num_samples = (samples * samples) as f32; 128 | let r = (color[0] / num_samples).powf(GAMMA); 129 | let g = (color[1] / num_samples).powf(GAMMA); 130 | let b = (color[2] / num_samples).powf(GAMMA); 131 | 132 | Color::new_f32(r, g, b) 133 | } 134 | 135 | fn trace(&self, ray: Ray, depth: u32, cull: bool) -> Color { 136 | if depth == 0 { 137 | return Color::black(); 138 | } 139 | 140 | let mut result = self.scene.clear_color; 141 | let possible_hit = self.scene.intersect(ray, cull); 142 | 143 | if let Some(hit) = possible_hit { 144 | let material = hit.shape.material(); 145 | 146 | result = match material.illumination_model { 147 | IllumninationModel::Constant => material.diffuse_color(hit.texture_coord), 148 | IllumninationModel::Diffuse => self.shade(&hit, ray, false), 149 | IllumninationModel::DiffuseSpecular => self.shade(&hit, ray, true), 150 | IllumninationModel::DiffuseSpecularReflective => { 151 | self.shade(&hit, ray, true) + self.reflect(&hit, ray, depth) 152 | } 153 | IllumninationModel::DiffuseSpecularReflectiveGlass => { 154 | self.shade(&hit, ray, true) + self.reflect(&hit, ray, depth) 155 | } 156 | IllumninationModel::DiffuseSpecularFresnel => { 157 | self.shade(&hit, ray, true) + self.reflect(&hit, ray, depth) 158 | } 159 | IllumninationModel::DiffuseSpecularRefracted => { 160 | let refraction_properties = RefractionProperties::new(&hit, &ray); 161 | 162 | let mut color = self.shade(&hit, ray, true) + self.reflect(&hit, ray, depth); 163 | if !refraction_properties.total_internal_reflection() { 164 | color = color + self.refract(&hit, ray, depth, &refraction_properties); 165 | } 166 | 167 | color 168 | } 169 | IllumninationModel::DiffuseSpecularRefractedFresnel => { 170 | let refraction_properties = RefractionProperties::new(&hit, &ray); 171 | let kr = self.fresnel(&refraction_properties); 172 | let kt = 1.0 - kr; 173 | 174 | let mut color = 175 | self.shade(&hit, ray, true) + self.reflect(&hit, ray, depth) * kr; 176 | if !refraction_properties.total_internal_reflection() { 177 | color = color + self.refract(&hit, ray, depth, &refraction_properties) * kt; 178 | } 179 | 180 | color 181 | } 182 | }; 183 | } 184 | 185 | result 186 | } 187 | 188 | fn shade(&self, intersection: &Intersection, original_ray: Ray, specular: bool) -> Color { 189 | let material: &Material = intersection.shape.material(); 190 | let mut result = 191 | material.ambient_color(intersection.texture_coord) * self.scene.ambient_color; 192 | 193 | // TODO: Move lights iteration to Scene 194 | for light in &self.scene.lights { 195 | let ray = light.create_shadow_ray(intersection, Some(original_ray.medium_refraction)); 196 | let distance_to_light = light.distance_to_light(intersection); 197 | if self 198 | .scene 199 | .first_intersection(ray, false, distance_to_light) 200 | .is_some() 201 | { 202 | continue; 203 | } 204 | 205 | if let Some(diffuse_color) = 206 | light.diffuse_color(intersection, material, distance_to_light) 207 | { 208 | result = result + diffuse_color; 209 | } 210 | 211 | // Specular 212 | if specular { 213 | if let Some(specular_color) = 214 | light.specular_color(intersection, material, &original_ray, distance_to_light) 215 | { 216 | result = result + specular_color; 217 | } 218 | } 219 | } 220 | 221 | result 222 | } 223 | 224 | fn reflect(&self, intersection: &Intersection, original_ray: Ray, current_depth: u32) -> Color { 225 | let new_direction = original_ray 226 | .direction 227 | .reflect(&intersection.normal) 228 | .normalize(); 229 | 230 | let new_ray = Ray::new( 231 | (intersection.point + new_direction * RAY_OFFSET).as_point(), 232 | new_direction, 233 | Some(original_ray.medium_refraction), 234 | ); 235 | 236 | let reflected_color = self.trace(new_ray, current_depth - 1, false); 237 | 238 | reflected_color 239 | * intersection 240 | .shape 241 | .material() 242 | .reflection_coefficient 243 | .unwrap_or(0.0) 244 | } 245 | 246 | fn refract( 247 | &self, 248 | intersection: &Intersection, 249 | original_ray: Ray, 250 | current_depth: u32, 251 | refraction_properties: &RefractionProperties, 252 | ) -> Color { 253 | assert!( 254 | intersection.shape.material().is_refractive(), 255 | "Don't call refract for materials that aren't refractive" 256 | ); 257 | let (c2, n, normal, cos_i, n2) = ( 258 | refraction_properties.c2, 259 | refraction_properties.n, 260 | refraction_properties.normal, 261 | refraction_properties.cos_i, 262 | refraction_properties.n2, 263 | ); 264 | 265 | if c2 > 0.0 { 266 | let direction = 267 | (original_ray.direction * n + normal * (n * cos_i - c2.sqrt())).normalize(); 268 | 269 | let new_ray = Ray::new( 270 | (intersection.point + direction * RAY_OFFSET).as_point(), 271 | direction, 272 | Some(n2), 273 | ); 274 | 275 | let refraction_color = self.trace(new_ray, current_depth - 1, false); 276 | let absorbance = intersection 277 | .shape 278 | .material() 279 | .ambient_color(intersection.texture_coord) 280 | * 0.15 281 | * -intersection.t; 282 | let transparency = Color::new_f32( 283 | absorbance.r_f32().exp(), 284 | absorbance.g_f32().exp(), 285 | absorbance.b_f32().exp(), 286 | ); 287 | 288 | return refraction_color * transparency; 289 | } 290 | 291 | Color::black() 292 | } 293 | 294 | fn fresnel(&self, refraction_properties: &RefractionProperties) -> f32 { 295 | let (n1, n2, cos_i) = ( 296 | refraction_properties.n1, 297 | refraction_properties.n2, 298 | refraction_properties.cos_i, 299 | ); 300 | let sin_t = n1 / n2 * (1.0 - cos_i * cos_i).max(0.0).sqrt(); 301 | let cos_t = (1.0 - sin_t * sin_t).max(0.0).sqrt(); 302 | let cos_i = cos_i.abs(); 303 | 304 | let rs = ((n2 * cos_i) - (n1 * cos_t)) / ((n2 * cos_i) + (n1 * cos_t)); 305 | let rp = ((n1 * cos_t) - (n2 * cos_i)) / ((n1 * cos_t) + (n2 * cos_i)); 306 | 307 | (rs * rs + rp * rp) / 2.0 308 | } 309 | } 310 | -------------------------------------------------------------------------------- /src/scene.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | use std::error::Error; 3 | use std::fmt; 4 | use std::path::Path; 5 | use std::rc::Rc; 6 | 7 | use tracing::warn; 8 | 9 | use crate::color::Color; 10 | use crate::config; 11 | use crate::config::Object; 12 | use crate::geometry::{ExtentVolume, Intersectable, Octree, Transformable}; 13 | use crate::geometry::{Plane, Sphere}; 14 | use crate::intersection::Intersection; 15 | use crate::light; 16 | use crate::material::Material; 17 | use crate::math::{Point3, Vector3}; 18 | use crate::mesh_loader::MeshLoader; 19 | use crate::ray::Ray; 20 | 21 | #[derive(Debug, Clone)] 22 | pub struct SceneConfigLoadError { 23 | description: String, 24 | } 25 | 26 | impl SceneConfigLoadError { 27 | fn new(description: String) -> Self { 28 | SceneConfigLoadError { description } 29 | } 30 | } 31 | 32 | impl fmt::Display for SceneConfigLoadError { 33 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 34 | write!(f, "Failed to load scene from config. {}", self.description) 35 | } 36 | } 37 | 38 | impl Error for SceneConfigLoadError { 39 | fn source(&self) -> Option<&(dyn Error + 'static)> { 40 | None 41 | } 42 | } 43 | 44 | pub struct Scene { 45 | pub objects: Vec>, 46 | pub lights: Vec>, 47 | pub ambient_color: Color, 48 | pub clear_color: Color, 49 | } 50 | 51 | impl Scene { 52 | pub fn new( 53 | objects: Vec>, 54 | lights: Vec>, 55 | ambient_color: Color, 56 | clear_color: Color, 57 | ) -> Scene { 58 | Scene { 59 | objects, 60 | lights, 61 | ambient_color, 62 | clear_color, 63 | } 64 | } 65 | 66 | pub fn new_from_config( 67 | scene: &config::Scene, 68 | materials: &HashMap>, 69 | mesh_loader: &mut MeshLoader, 70 | fallback_material: Rc, 71 | ) -> Result { 72 | let mut objects: Vec> = vec![]; 73 | 74 | for object in &scene.objects { 75 | match *object { 76 | Object::Sphere { 77 | radius, 78 | ref transforms, 79 | ref material_name, 80 | } => { 81 | let material = match material_name { 82 | None => fallback_material.clone(), 83 | Some(name) => { 84 | assert!( 85 | materials.contains_key(name), 86 | "Invalid material name: {}", 87 | name 88 | ); 89 | materials[name].clone() 90 | } 91 | }; 92 | let mut sphere = Box::new(Sphere::new(Point3::at_origin(), radius, material)); 93 | Self::apply_transforms(sphere.as_mut() as &mut dyn Transformable, transforms); 94 | objects.push(sphere as Box); 95 | } 96 | Object::Plane { 97 | normal, 98 | ref transforms, 99 | ref material_name, 100 | } => { 101 | let material = match material_name { 102 | None => fallback_material.clone(), 103 | Some(name) => { 104 | assert!( 105 | materials.contains_key(name), 106 | "Invalid material name: {}", 107 | name 108 | ); 109 | materials[name].clone() 110 | } 111 | }; 112 | 113 | let mut plane = Box::new(Plane::new( 114 | Point3::at_origin(), 115 | Vector3::from(normal), 116 | material, 117 | )); 118 | Self::apply_transforms(plane.as_mut() as &mut dyn Transformable, transforms); 119 | objects.push(plane as Box); 120 | } 121 | Object::Mesh { 122 | ref path, 123 | ref transforms, 124 | ref material_name, 125 | } => { 126 | let material = match material_name { 127 | None => fallback_material.clone(), 128 | Some(name) => { 129 | assert!( 130 | materials.contains_key(name), 131 | "Invalid material name: {}", 132 | name 133 | ); 134 | materials[name].clone() 135 | } 136 | }; 137 | 138 | let mut meshes = match mesh_loader.load(Path::new(&path), material) { 139 | Ok(meshes) => meshes, 140 | Err(error) => { 141 | warn!("Failed to load scene: {}", error); 142 | return Err(SceneConfigLoadError::new(error.to_string())); 143 | } 144 | }; 145 | 146 | for mesh in &mut meshes { 147 | Self::apply_transforms(mesh.as_mut() as &mut dyn Transformable, transforms); 148 | } 149 | let mut intersectables = meshes 150 | .into_iter() 151 | .map(|mesh| mesh as Box) 152 | .collect(); 153 | 154 | objects.append(&mut intersectables); 155 | } 156 | Object::MeshInstance { 157 | ref path, 158 | ref transforms, 159 | ref material_name, 160 | } => { 161 | let material = match material_name { 162 | None => fallback_material.clone(), 163 | Some(name) => { 164 | assert!( 165 | materials.contains_key(name), 166 | "Invalid material name: {}", 167 | name 168 | ); 169 | materials[name].clone() 170 | } 171 | }; 172 | 173 | let mut meshes = match mesh_loader.load_instance(Path::new(&path), material) { 174 | Ok(meshes) => meshes, 175 | Err(error) => { 176 | warn!("Failed to load scene: {}", error); 177 | return Err(SceneConfigLoadError::new(error.to_string())); 178 | } 179 | }; 180 | 181 | for mesh in &mut meshes { 182 | Self::apply_transforms(mesh.as_mut() as &mut dyn Transformable, transforms); 183 | } 184 | let mut intersectables = meshes 185 | .into_iter() 186 | .map(|mesh| mesh as Box) 187 | .collect(); 188 | 189 | objects.append(&mut intersectables); 190 | } 191 | } 192 | } 193 | 194 | let lights: Vec> = scene 195 | .lights 196 | .iter() 197 | .map(|light| match *light { 198 | config::Light::PointLight { 199 | origin, 200 | color, 201 | intensity, 202 | falloff, 203 | diffuse, 204 | specular, 205 | } => Box::new(light::Point::new( 206 | Point3::from(origin), 207 | Color::from(color), 208 | intensity, 209 | falloff.unwrap_or(light::Falloff::InverseSquare), 210 | diffuse.unwrap_or(true), 211 | specular.unwrap_or(true), 212 | )) as Box, 213 | 214 | config::Light::DirectionalLight { 215 | direction, 216 | color, 217 | intensity, 218 | diffuse, 219 | specular, 220 | } => Box::new(light::Directional::new( 221 | Vector3::from(direction), 222 | Color::from(color), 223 | intensity, 224 | diffuse.unwrap_or(true), 225 | specular.unwrap_or(true), 226 | )) as Box, 227 | }) 228 | .collect(); 229 | 230 | Ok(Self::new( 231 | objects, 232 | lights, 233 | Color::from(scene.ambient_color), 234 | Color::from(scene.clear_color), 235 | )) 236 | } 237 | 238 | fn apply_transforms( 239 | shape: &mut dyn Transformable, 240 | transforms: &Option>, 241 | ) { 242 | if let Some(ref transforms_to_apply) = *transforms { 243 | let converted_transforms: Vec<_> = transforms_to_apply 244 | .iter() 245 | .map(|t| t.to_transform()) 246 | .collect(); 247 | 248 | shape.apply_transforms(&converted_transforms); 249 | } 250 | } 251 | 252 | pub fn intersect(&self, ray: Ray, cull: bool) -> Option { 253 | let mut closest_intersection: Option = None; 254 | 255 | for shape in &self.objects { 256 | if let Some(intersection) = shape.intersect(ray, cull) { 257 | if let Some(closest) = closest_intersection { 258 | if intersection.t < closest.t { 259 | closest_intersection = Some(intersection) 260 | } 261 | } else { 262 | closest_intersection = Some(intersection) 263 | } 264 | } 265 | } 266 | 267 | closest_intersection 268 | } 269 | 270 | pub fn first_intersection(&self, ray: Ray, cull: bool, distance: f32) -> Option { 271 | for object in &self.objects { 272 | if let Some(hit) = object.intersect(ray, cull) { 273 | if hit.t < distance { 274 | return Some(hit); 275 | } 276 | } 277 | } 278 | 279 | None 280 | } 281 | } 282 | -------------------------------------------------------------------------------- /src/texture/file.rs: -------------------------------------------------------------------------------- 1 | use std::error::Error; 2 | use std::fmt; 3 | use std::path::PathBuf; 4 | 5 | use image; 6 | use image::GenericImageView; 7 | 8 | use crate::color::Color; 9 | 10 | use super::{Texture, TextureCoord}; 11 | 12 | #[derive(Debug)] 13 | pub struct FileError { 14 | description: String, 15 | cause: image::ImageError, 16 | } 17 | 18 | impl From for FileError { 19 | fn from(cause: image::ImageError) -> Self { 20 | let description = cause.to_string(); 21 | FileError { cause, description } 22 | } 23 | } 24 | 25 | impl fmt::Display for FileError { 26 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 27 | write!(f, "failed to load texture with error: {}", self.cause) 28 | } 29 | } 30 | 31 | impl Error for FileError { 32 | fn description(&self) -> &str { 33 | self.description.as_ref() 34 | } 35 | 36 | fn cause(&self) -> Option<&dyn Error> { 37 | Some(&self.cause) 38 | } 39 | } 40 | 41 | pub struct File { 42 | image: image::DynamicImage, 43 | path: PathBuf, 44 | } 45 | 46 | impl File { 47 | pub fn new(path: PathBuf) -> Result { 48 | let image = image::open(&path)?; 49 | Ok(File { path, image }) 50 | } 51 | } 52 | 53 | impl Texture for File { 54 | fn lookup(&self, uv: TextureCoord) -> Color { 55 | // assert!( 56 | // uv.x >= 0.0 && uv.x <= 1.0 && uv.y >= 0.0 && uv.y <= 1.0, 57 | // "Incorrect uv coordinate: {:?}", 58 | // uv 59 | // ); 60 | let (bounded_u, boundex_v) = (uv.x.abs() % 1.0, uv.y.abs() % 1.0); 61 | let (width, height) = self.image.dimensions(); 62 | let x = (f64::from(bounded_u) * ((width - 1) as f64)).round() as u32; 63 | let y = (f64::from(boundex_v) * ((height - 1) as f64)).round() as u32; 64 | 65 | let pixel = self.image.get_pixel(x, y); 66 | 67 | Color::new(pixel[0], pixel[1], pixel[2]) 68 | } 69 | } 70 | 71 | impl fmt::Debug for File { 72 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 73 | write!(f, "File {{ path: {:?} }}", self.path) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/texture/mod.rs: -------------------------------------------------------------------------------- 1 | use std::fmt::Debug; 2 | 3 | use crate::color::Color; 4 | use crate::math::Point2; 5 | 6 | pub mod file; 7 | pub mod procedural; 8 | mod solid; 9 | 10 | pub use self::file::File; 11 | pub use self::procedural::Procedural; 12 | pub use self::solid::Solid; 13 | 14 | pub type TextureCoord = Point2; 15 | 16 | pub trait Texture: Debug { 17 | fn lookup(&self, uv: TextureCoord) -> Color; 18 | } 19 | -------------------------------------------------------------------------------- /src/texture/procedural/checkerboard.rs: -------------------------------------------------------------------------------- 1 | use crate::color::Color; 2 | 3 | use super::TextureCoord; 4 | 5 | const SCALE: f32 = 5.0; 6 | const ROTATION: f32 = std::f32::consts::PI / 4.0; 7 | 8 | fn modulo(v: f32) -> f32 { 9 | v - v.floor() 10 | } 11 | 12 | pub fn checkerboard(coord: TextureCoord) -> Color { 13 | let s = coord.x * ROTATION.cos() - coord.y * ROTATION.sin(); 14 | let t = coord.y * ROTATION.cos() + coord.x * ROTATION.sin(); 15 | 16 | let s_v = modulo(s * SCALE) < 0.5; 17 | let t_v = modulo(t * SCALE) < 0.5; 18 | 19 | match (s_v, t_v) { 20 | (true, false) => Color::black(), 21 | (false, true) => Color::black(), 22 | _ => Color::white(), 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/texture/procedural/julia.rs: -------------------------------------------------------------------------------- 1 | use super::TextureCoord; 2 | use crate::color::Color; 3 | use crate::math::Complex; 4 | 5 | const MAX: Complex = Complex::new(2.0, 2.0); 6 | const MIN: Complex = Complex::new(-2.0, -2.0); 7 | const MAX_ITERATIONS: usize = 200; 8 | 9 | pub fn julia(coord: TextureCoord) -> Color { 10 | let c = Complex::new(-0.8, 0.156); 11 | let mut z = Complex::new( 12 | MIN.real + (coord.x as f64 % 1.0) * (MAX.real - MIN.real), 13 | MIN.im + (1.0 - (coord.y as f64 % 1.0)) * (MAX.im - MIN.im), 14 | ); 15 | 16 | for i in 0..MAX_ITERATIONS { 17 | z.square_mut(); 18 | z.add_mut(&c); 19 | 20 | if z.dot() > 4.0 { 21 | let smooth_i = i as f64 + 1.0 - (z.abs().log2() / 2.0_f64.log2()).log2(); 22 | 23 | let hue = 250.0 + 360.0 * (smooth_i as f32 / MAX_ITERATIONS as f32); 24 | let saturation = 1.0; 25 | let value = 1.0; 26 | 27 | return Color::new_hsv(hue, saturation, value); 28 | } 29 | } 30 | 31 | Color::black() 32 | } 33 | -------------------------------------------------------------------------------- /src/texture/procedural/mandelbrot.rs: -------------------------------------------------------------------------------- 1 | use super::TextureCoord; 2 | use crate::color::Color; 3 | use crate::math::Complex; 4 | 5 | const MAX: Complex = Complex::new(1.0, 1.2); 6 | const MIN: Complex = Complex::new(-2.1, -1.2); 7 | const MAX_ITERATIONS: usize = 130; 8 | 9 | pub fn mandelbrot(coord: TextureCoord) -> Color { 10 | let mut z = Complex::default(); 11 | let c = Complex::new( 12 | MIN.real + (coord.x as f64 % 1.0) * (MAX.real - MIN.real), 13 | MIN.im + (1.0 - (coord.y as f64 % 1.0)) * (MAX.im - MIN.im), 14 | ); 15 | 16 | for i in 0..MAX_ITERATIONS { 17 | z.square_mut(); 18 | z.add_mut(&c); 19 | 20 | if z.dot() > 400.0 { 21 | let smooth_i = i as f64 + 1.0 - (z.abs().ln() / 4.0_f64.ln()).ln(); 22 | 23 | let hue = 250.0 + 360.0 * (smooth_i as f32 / MAX_ITERATIONS as f32); 24 | let saturation = 1.0; 25 | let value = 1.0; 26 | 27 | return Color::new_hsv(hue, saturation, value); 28 | } 29 | } 30 | 31 | Color::black() 32 | } 33 | -------------------------------------------------------------------------------- /src/texture/procedural/mod.rs: -------------------------------------------------------------------------------- 1 | mod checkerboard; 2 | mod julia; 3 | mod mandelbrot; 4 | 5 | use std::clone::Clone; 6 | use std::fmt; 7 | 8 | use crate::color::Color; 9 | 10 | use super::{Texture, TextureCoord}; 11 | 12 | #[derive(Clone)] 13 | pub struct Procedural { 14 | callback: F, 15 | } 16 | 17 | impl Procedural 18 | where 19 | F: Fn(TextureCoord) -> Color, 20 | { 21 | pub fn new(callback: F) -> Self { 22 | Procedural { callback } 23 | } 24 | } 25 | 26 | impl Texture for Procedural 27 | where 28 | F: Fn(TextureCoord) -> Color, 29 | { 30 | fn lookup(&self, uv: TextureCoord) -> Color { 31 | (self.callback)(uv) 32 | } 33 | } 34 | 35 | impl fmt::Debug for Procedural 36 | where 37 | F: Fn(TextureCoord) -> Color, 38 | { 39 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 40 | write!(f, "Procedural {{ }}") 41 | } 42 | } 43 | 44 | impl Procedural Color> { 45 | pub fn mandelbrot() -> Self { 46 | Self::new(mandelbrot::mandelbrot) 47 | } 48 | 49 | pub fn julia() -> Self { 50 | Self::new(julia::julia) 51 | } 52 | 53 | pub fn checkerboard() -> Self { 54 | Self::new(checkerboard::checkerboard) 55 | } 56 | } 57 | 58 | #[cfg(test)] 59 | mod test { 60 | use super::{Color, Procedural, Texture, TextureCoord}; 61 | 62 | #[test] 63 | fn test_lookup() { 64 | let t = Procedural::new(|uv| Color::new_f32(uv.x, uv.y, 0.0)); 65 | let c1 = TextureCoord::new(0.0, 1.0); 66 | let c2 = TextureCoord::new(1.0, 0.0); 67 | let c3 = TextureCoord::new(0.0, 0.0); 68 | 69 | assert_eq!(t.lookup(c1), Color::new(0, 255, 0)); 70 | assert_eq!(t.lookup(c2), Color::new(255, 0, 0)); 71 | assert_eq!(t.lookup(c3), Color::new(0, 0, 0)); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/texture/solid.rs: -------------------------------------------------------------------------------- 1 | use crate::color::Color; 2 | 3 | use super::{Texture, TextureCoord}; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct Solid { 7 | color: Color, 8 | } 9 | 10 | impl Solid { 11 | pub fn new(color: Color) -> Self { 12 | Solid { color } 13 | } 14 | } 15 | 16 | impl Texture for Solid { 17 | fn lookup(&self, _uv: TextureCoord) -> Color { 18 | self.color 19 | } 20 | } 21 | 22 | #[cfg(test)] 23 | mod test { 24 | use super::{Color, Solid, Texture, TextureCoord}; 25 | 26 | #[test] 27 | fn test_lookup() { 28 | let c = Color::new(255, 127, 53); 29 | let t = Solid::new(c); 30 | let c1 = TextureCoord::new(0.0, 1.0); 31 | let c2 = TextureCoord::new(1.0, 0.0); 32 | let c3 = TextureCoord::new(0.0, 0.0); 33 | 34 | assert_eq!(t.lookup(c1), c); 35 | assert_eq!(t.lookup(c2), c); 36 | assert_eq!(t.lookup(c3), c); 37 | } 38 | } 39 | --------------------------------------------------------------------------------