Compare commits

..

No commits in common. "e2dd9f7e3a0f69db1293008fa099d1c48c871f26" and "ed6e84a240a1d5bd3fb46412ccba9c2af4d40571" have entirely different histories.

14 changed files with 310 additions and 636 deletions

4
.gitignore vendored
View file

@ -3,6 +3,10 @@
debug/
target/
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
Cargo.lock
# These are backup files generated by rustfmt
**/*.rs.bk

369
Cargo.lock generated
View file

@ -1,369 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "alga"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f823d037a7ec6ea2197046bafd4ae150e6bc36f9ca347404f46a46823fa84f2"
dependencies = [
"approx",
"num-complex",
"num-traits",
]
[[package]]
name = "approx"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0e60b75072ecd4168020818c0107f2857bb6c4e64252d8d3983f6263b40a5c3"
dependencies = [
"num-traits",
]
[[package]]
name = "autocfg"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cloudabi"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
dependencies = [
"bitflags",
]
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "generic-array"
version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd"
dependencies = [
"typenum",
]
[[package]]
name = "getrandom"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "libc"
version = "0.2.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e"
[[package]]
name = "libm"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
[[package]]
name = "matrixmultiply"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "916806ba0031cd542105d916a97c8572e1fa6dd79c9c51e7eb43a09ec2dd84c1"
dependencies = [
"rawpointer",
]
[[package]]
name = "nalgebra"
version = "0.18.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aaa9fddbc34c8c35dd2108515587b8ce0cab396f17977b8c738568e4edb521a2"
dependencies = [
"alga",
"approx",
"generic-array",
"matrixmultiply",
"num-complex",
"num-rational",
"num-traits",
"rand 0.6.5",
"typenum",
]
[[package]]
name = "num-complex"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95"
dependencies = [
"autocfg 1.0.1",
"num-traits",
]
[[package]]
name = "num-integer"
version = "0.1.44"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db"
dependencies = [
"autocfg 1.0.1",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef"
dependencies = [
"autocfg 1.0.1",
"num-integer",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg 1.0.1",
"libm",
]
[[package]]
name = "ppv-lite86"
version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[package]]
name = "rand"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
dependencies = [
"autocfg 0.1.7",
"libc",
"rand_chacha 0.1.1",
"rand_core 0.4.2",
"rand_hc 0.1.0",
"rand_isaac",
"rand_jitter",
"rand_os",
"rand_pcg",
"rand_xorshift",
"winapi",
]
[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
"getrandom",
"libc",
"rand_chacha 0.2.2",
"rand_core 0.5.1",
"rand_hc 0.2.0",
]
[[package]]
name = "rand_chacha"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
dependencies = [
"autocfg 0.1.7",
"rand_core 0.3.1",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
"ppv-lite86",
"rand_core 0.5.1",
]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
"rand_core 0.4.2",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
"getrandom",
]
[[package]]
name = "rand_hc"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
"rand_core 0.5.1",
]
[[package]]
name = "rand_isaac"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rand_jitter"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
dependencies = [
"libc",
"rand_core 0.4.2",
"winapi",
]
[[package]]
name = "rand_os"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
dependencies = [
"cloudabi",
"fuchsia-cprng",
"libc",
"rand_core 0.4.2",
"rdrand",
"winapi",
]
[[package]]
name = "rand_pcg"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
dependencies = [
"autocfg 0.1.7",
"rand_core 0.4.2",
]
[[package]]
name = "rand_xorshift"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "rawpointer"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
dependencies = [
"rand_core 0.3.1",
]
[[package]]
name = "render"
version = "0.1.0"
dependencies = [
"nalgebra",
"rand 0.7.3",
]
[[package]]
name = "typenum"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"

21
LICENSE
View file

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2021 Bijan Sheibani
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -30,7 +30,7 @@ This list may be changed or extended in the future.
- [ ] Point light sources
- [x] Point source struct
- [x] Point source illuminance test
- [x] Hard shadows
- [ ] Hard shadows
- [ ] Soft shadows
- [ ] ~~Light-emitting surfaces~~
- [ ] Indirect lighting

View file

@ -1,10 +1,10 @@
extern crate nalgebra as na;
use std::time::Instant;
use std::fs::File;
use std::io::Write;
use na::*;
use na::geometry::Point3;
mod camera; use camera::*;
mod types; use types::*;
@ -36,24 +36,15 @@ fn render(camera: &Camera, scene: &Scene, filename: &str) -> std::io::Result<()>
fn main() -> std::io::Result<()> {
let camera = Camera::new(Point3::new(0.0,5.0,0.0), Vector3::new(0.0,-1.0,0.0), 1.0, 16.0 / 9.0, 2.0, 720);
let camera = Camera::new(Point3::new(0.0,0.0,0.0), Vector3::new(0.0,0.0,1.0), 1.0, 16.0 / 9.0, 2.0, 480);
let scene = Scene {
objects: vec![
Object::new(Plane::xz(|_, _| Texture { color: Color::white(), albedo: 0.8 })),
Object::new(TriangleMesh::singleton(Point3::new(-1.0, -1.0, 2.0), Point3::new(0.0, 1.0, 2.0), Point3::new(1.0, -1.0, 2.0), |t, u, v| Color::new(t, u, v)))
],
lights: vec![
],
background: Color::gray(0.5)
lights: Vec::new(),
background: Color::black()
};
let before = Instant::now();
render(&camera, &scene, "out.ppm")?;
println!("{}", before.elapsed().as_millis());
Ok(())
render(&camera, &scene, "out.ppm")
}

View file

@ -3,7 +3,9 @@ mod sphere; pub use sphere::*;
mod plane; pub use plane::*;
mod triangle; pub use triangle::*;
mod bound; pub use bound::*;
mod point_light; pub use point_light::*;
mod pointlight; pub use pointlight::*;
use na::*;
use crate::types::*;
@ -17,11 +19,11 @@ pub trait Surface {
// Takes in a point (assumed to be on the object's surface)
// and returns the normal vector off of that point.
fn normal(&self, point: Point3f) -> Unit3f;
fn normal(&self, point: Point3<f32>) -> Unit<Vector3<f32>>;
// Takes in a point (assumed to be on the object's surface)
// and returns the texture information on that point.
fn get_texture(&self, point: Point3f) -> Texture;
// and returns the color information on that point.
fn getcolor(&self, point: Point3<f32>) -> Color;
// Creates a bounding sphere around the object.
fn bound(&self) -> Bound;
@ -38,7 +40,7 @@ impl Object {
let bound = surface.bound();
Object {
surface: Box::new(surface),
bound
bound: bound
}
}
@ -48,22 +50,14 @@ impl Object {
self.surface.intersect(ray)
} else { None }
}
pub fn normal(&self, point: Point3f) -> Unit3f { self.surface.normal(point) }
pub fn get_texture(&self, point: Point3f) -> Texture { self.surface.get_texture(point) }
pub fn normal(&self, point: Point3<f32>) -> Unit<Vector3<f32>> { self.surface.normal(point) }
pub fn getcolor(&self, point: Point3<f32>) -> Color { self.surface.getcolor(point) }
}
pub trait Light {
// Determine if the light is able to illuminate the point.
fn check_shadow(&self, point: Point3f, objects: &Vec<Object>) -> bool;
// Compute color on a point.
fn get_color(&self, point: Point3f) -> Color;
// Compute intensity on a point.
fn intensity(&self, point: Point3f) -> f32;
// Return the direction from the point to the light source.
fn direction(&self, point: Point3f) -> Unit3f;
// If so, return the color of the light.
fn illuminate(&self, point: Point3<f32>, objects: &Vec<Object>) -> Option<Color>;
}
pub struct Scene {
@ -71,3 +65,17 @@ pub struct Scene {
pub lights: Vec<Box<dyn Light>>,
pub background: Color
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn obj_getcolor() {
let sphere = Object::new(Sphere::new_solid(0.0, 0.0, 0.0, 1.0, Color::white()));
let point = Point3::new(1.0, 0.0, 0.0);
assert_eq!(sphere.getcolor(point), Color::white());
}
}

View file

@ -3,13 +3,13 @@ extern crate nalgebra as na;
// use na::distance;
use na::geometry::Point3;
use crate::types::*;
use crate::types::Ray;
// A bounding sphere, used for
// intersection test optimization.
#[derive(Debug)]
pub struct Bound {
pub center: Point3f,
pub center: Point3<f32>,
pub radius: f32,
// If true, then the bounding sphere is disabled.
@ -24,7 +24,7 @@ impl Bound {
l.norm_squared() >= self.radius * self.radius
}
// pub fn contains(&self, point: &Point3f) -> bool { distance(&self.center, point) < self.radius }
// pub fn contains(&self, point: &Point3<f32>) -> bool { distance(&self.center, point) < self.radius }
pub fn bypass() -> Self { Bound { center: Point3::origin(), radius: 0.0, bypass: true } }
}

View file

@ -7,24 +7,24 @@ use crate::types::*;
use super::{Surface, bound::*};
pub struct Plane {
pub center: Point3f, // Plane origin (used for texture mapping).
pub normal: Unit3f, // Precomputed plane normal.
pub center: Point3<f32>, // Plane origin (used for texture mapping).
pub normal: Unit<Vector3<f32>>, // Precomputed plane normal.
x_axis: Vector3f, // Plane x-axis (The 3D direction that corresponds to the x-direction on the plane).
y_axis: Vector3f, // Plane y-axis (The 3D direction that corresponds to the y-direction on the plane).
x_axis: Vector3<f32>, // Plane x-axis (The 3D direction that corresponds to the x-direction on the plane).
y_axis: Vector3<f32>, // Plane y-axis (The 3D direction that corresponds to the y-direction on the plane).
texture: Box<dyn Fn(f32, f32) -> Texture> // Texture map.
texture: Box<dyn Fn(f32, f32) -> Color> // Texture map.
// Input coordinates are defined in terms of the axes above.
}
#[allow(dead_code)]
impl Plane {
// Creates a new plane.
pub fn new<F: 'static>(center: Point3f, x_axis: Vector3f, y_axis: Vector3f, texture: F) -> Self
where F: Fn(f32, f32) -> Texture
pub fn new<F: 'static>(center: Point3<f32>, x_axis: Vector3<f32>, y_axis: Vector3<f32>, texture: F) -> Self
where F: Fn(f32, f32) -> Color
{
Plane {
center,
center: center,
normal: Unit::new_normalize(x_axis.cross(&y_axis)),
x_axis: x_axis,
y_axis: y_axis,
@ -33,8 +33,8 @@ impl Plane {
}
// Creates a new plane with the normal flipped.
pub fn new_flip<F: 'static>(center: Point3f, x_axis: Vector3f, y_axis: Vector3f, texture: F) -> Self
where F: Fn(f32, f32) -> Texture
pub fn new_flip<F: 'static>(center: Point3<f32>, x_axis: Vector3<f32>, y_axis: Vector3<f32>, texture: F) -> Self
where F: Fn(f32, f32) -> Color
{
Plane {
center: center,
@ -46,20 +46,20 @@ impl Plane {
}
// Creates a new plane of a solid color.
pub fn new_solid(center: Point3f, x_axis: Vector3f, y_axis: Vector3f, texture: Texture) -> Self
{ Plane::new(center, x_axis, y_axis, move |_, _| texture) }
pub fn new_solid(center: Point3<f32>, x_axis: Vector3<f32>, y_axis: Vector3<f32>, color: Color) -> Self
{ Plane::new(center, x_axis, y_axis, move |_, _| color) }
// Creates a new flipped plane of a solid color.
pub fn new_solid_flip(center: Point3f, x_axis: Vector3f, y_axis: Vector3f, texture: Texture) -> Self
{ Plane::new_flip(center, x_axis, y_axis, move |_, _| texture) }
pub fn new_solid_flip(center: Point3<f32>, x_axis: Vector3<f32>, y_axis: Vector3<f32>, color: Color) -> Self
{ Plane::new_flip(center, x_axis, y_axis, move |_, _| color) }
// Creates a new XY-plane with the given texture map.
pub fn xy(texture: impl 'static + Fn(f32, f32) -> Texture) -> Self
pub fn xy<F: 'static + Fn(f32, f32) -> Color>(texture: F) -> Self
{ Plane::new(Point3::origin(), Vector3::x(), Vector3::y(), texture) }
// Creates a new XZ-plane with the given texture map.
pub fn xz(texture: impl 'static + Fn(f32, f32) -> Texture) -> Self
pub fn xz<F: 'static + Fn(f32, f32) -> Color>(texture: F) -> Self
{ Plane::new(Point3::origin(), Vector3::x(), Vector3::z(), texture) }
}
@ -67,7 +67,7 @@ impl Surface for Plane {
fn intersect(&self, ray: Ray) -> Option<f32> {
let d = self.normal.dot(&ray.direction);
if d > -1e-3 { return None; }
if d < 1e-3 { return None; }
let t = (self.center - ray.origin).dot(&*self.normal) / d;
@ -75,9 +75,9 @@ impl Surface for Plane {
else { None }
}
fn normal(&self, _point: Point3f) -> Unit3f { self.normal }
fn normal(&self, _point: Point3<f32>) -> Unit<Vector3<f32>> { self.normal }
fn get_texture(&self, point: Point3f) -> Texture {
fn getcolor(&self, point: Point3<f32>) -> Color {
let rel_pos = point - self.center;
let proj_point3 = rel_pos - (*self.normal * self.normal.dot(&rel_pos));
@ -91,3 +91,36 @@ impl Surface for Plane {
// bounding sphere could possibly contain one.
fn bound(&self) -> Bound { Bound::bypass() }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn plane_new() {
let plane = Plane::xy(|_, _| Color::black());
assert_eq!(plane.center, Point3::new(0.0, 0.0, 0.0));
assert_eq!(plane.normal, Unit::new_unchecked(Vector3::z()));
}
#[test]
fn plane_intersect() {
const N: f32 = 5.0;
let plane = Plane::xz(|_, _| Color::black());
let ray = Ray::new(Point3::new(0.0, N, 0.0), Vector3::new(0.0, -1.0, 0.0));
assert_eq!(plane.intersect(ray), Some(N));
}
#[test]
fn plane_getcolor() {
const N: f32 = 5.0;
let plane = Plane::xz(|x, y| Color::new(x, y, 0.0));
let point = Point3::new(5.0, 7.0, 6.0);
assert_eq!(plane.getcolor(point), Color::new(5.0, 6.0, 0.0));
}
}

View file

@ -1,50 +0,0 @@
extern crate nalgebra as na;
use na::*;
use crate::types::*;
use super::*;
pub struct PointLight {
pub pos: Point3f,
pub color: Color,
pub intensity: f32
}
#[allow(dead_code)]
impl PointLight {
pub fn new(pos: Point3f, color: Color, intensity: f32) -> PointLight {
PointLight { pos, color, intensity }
}
}
impl Light for PointLight {
fn check_shadow(&self, point: Point3f, objects: &Vec<Object>) -> bool {
let max_d = distance(&self.pos, &point);
objects.iter()
.filter_map(|obj| obj.intersect(Ray::from_points(self.pos, point)))
.all(|d| d - max_d > -1e-3 )
}
fn get_color(&self, _point: Point3f) -> Color { self.color }
fn intensity(&self, _point: Point3f) -> f32 { self.intensity }
fn direction(&self, point: Point3f) -> Unit3f {
Unit::new_normalize(self.pos - point)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn point_light_check_shadow() {
let light = PointLight::new(Point3::new(0.0, 1.0, 0.0), Color::white(), 1.0);
let block = Object::new(Sphere::new_solid(0.0, 0.5, 0.0, 0.1, Texture::new(0.0, 0.0, 0.0, 0.0)));
assert!(light.check_shadow(Point3::origin(), &Vec::new()));
assert!(!light.check_shadow(Point3::origin(), &vec![block]));
}
}

36
src/object/pointlight.rs Normal file
View file

@ -0,0 +1,36 @@
extern crate nalgebra as na;
use na::*;
use na::geometry::Point3;
use crate::types::*;
use super::*;
pub struct PointLight {
pub pos: Point3<f32>,
pub color: Color
}
impl PointLight {
pub fn new(pos: Point3<f32>, color: Color) -> PointLight {
PointLight {
pos: pos,
color: color
}
}
fn check_point(&self, point: Point3<f32>, objects: &Vec<Object>) -> bool {
let max_d = distance(&self.pos, &point);
objects.iter()
.filter_map(|obj| obj.intersect(Ray::from_points(self.pos, point)))
.all(|d| d > max_d)
}
}
impl Light for PointLight {
fn illuminate(&self, point: Point3<f32>, objects: &Vec<Object>) -> Option<Color> {
if self.check_point(point, objects) {
Some(self.color)
} else { None }
}
}

View file

@ -9,10 +9,10 @@ use crate::types::*;
use super::{Surface, bound::*};
pub struct Sphere {
pub center: Point3f, // Center point of the sphere.
pub center: Point3<f32>, // Center point of the sphere.
pub radius: f32, // Radius of the sphere.
texture: Box<dyn Fn(f32, f32) -> Texture> // Texture map.
texture: Box<dyn Fn(f32, f32) -> Color> // Texture map.
// Uses spherical coordinates (normalized from 0-1) as input.
}
@ -20,17 +20,18 @@ pub struct Sphere {
impl Sphere {
// Creates a new sphere.
pub fn new<F: 'static>(x: f32, y: f32, z: f32, radius: f32, texture: F) -> Self
where F: Fn(f32, f32) -> Texture
where F: Fn(f32, f32) -> Color
{
Sphere {
center: Point3::new(x, y, z), radius,
center: Point3::new(x, y, z),
radius: radius,
texture: Box::new(texture)
}
}
// Creates a new sphere of a solid color.
pub fn new_solid(x: f32, y: f32, z: f32, radius: f32, texture: Texture) -> Self
{ Sphere::new(x, y, z, radius, move |_, _| texture) }
pub fn new_solid(x: f32, y: f32, z: f32, radius: f32, color: Color) -> Self
{ Sphere::new(x, y, z, radius, move |_, _| color) }
}
impl Surface for Sphere {
@ -61,14 +62,14 @@ impl Surface for Sphere {
else { None }
}
fn normal(&self, point: Point3f) -> Unit3f {
fn normal(&self, point: Point3<f32>) -> Unit<Vector3<f32>> {
Unit::new_normalize(point - self.center)
}
fn get_texture(&self, point: Point3f) -> Texture {
fn getcolor(&self, point: Point3<f32>) -> Color {
let normal = self.normal(point);
// In this particular case, the normal is similar to a point on a unit sphere
// In this particular case, the normal is simular to a point on a unit sphere
// centred around the origin. We can thus use the normal coordinates to compute
// the spherical coordinates of the point.
let x = 0.5 + normal.z.atan2(normal.x) / (2.0 * PI);
@ -77,5 +78,5 @@ impl Surface for Sphere {
(*self.texture)(x, y)
}
fn bound(&self) -> Bound { Bound { center: self.center, radius: self.radius, bypass: false } }
fn bound(&self) -> Bound { Bound::bypass() }
}

View file

@ -13,36 +13,36 @@ pub struct Triangle {
pub v2: usize,
pub v3: usize,
normal: Unit3f, // Precalculated normal vector.
normal: Unit<Vector3<f32>>, // Precalculated normal vector.
area: f32, // Precalculated area for barycentric calculations.
texture: Box<dyn Fn(f32, f32, f32) -> Texture> // Texture map.
texture: Box<dyn Fn(f32, f32, f32) -> Color> // Texture map.
// Uses barycentric coordinates as input.
}
pub struct TriangleMesh {
pub vertices: Vec<Point3f>,
pub triangles: Vec<Triangle>
pub vertices: Vec<Point3<f32>>,
pub tris: Vec<Triangle>
}
fn tri_area(a: &Point3f, b: &Point3f, c: &Point3f) -> f32 {
fn tri_area(a: &Point3<f32>, b: &Point3<f32>, c: &Point3<f32>) -> f32 {
let prlg_area: f32 = (b - a).cross(&(c - a)).norm();
prlg_area / 2.0
}
impl Triangle {
fn vertex1<'a>(&self, vertices: &'a Vec<Point3f>) -> &'a Point3f { &vertices[self.v1] }
fn vertex2<'a>(&self, vertices: &'a Vec<Point3f>) -> &'a Point3f { &vertices[self.v2] }
fn vertex3<'a>(&self, vertices: &'a Vec<Point3f>) -> &'a Point3f { &vertices[self.v3] }
fn vertex1<'a>(&self, vertices: &'a Vec<Point3<f32>>) -> &'a Point3<f32> { &vertices[self.v1] }
fn vertex2<'a>(&self, vertices: &'a Vec<Point3<f32>>) -> &'a Point3<f32> { &vertices[self.v2] }
fn vertex3<'a>(&self, vertices: &'a Vec<Point3<f32>>) -> &'a Point3<f32> { &vertices[self.v3] }
// Conversion of barycentric coordinates to
// a point on the triangle.
fn from_bary(&self, vertices: &Vec<Point3f>, t: f32, u: f32, v: f32) -> Point3f {
fn from_bary(&self, vertices: &Vec<Point3<f32>>, t: f32, u: f32, v: f32) -> Point3<f32> {
Point::from(t * self.vertex1(vertices).coords + u * self.vertex2(vertices).coords + v * self.vertex3(vertices).coords)
}
// Conversion of a point to barycentric coordinates.
fn to_bary(&self, vertices: &Vec<Point3f>, point: Point3f) -> (f32, f32, f32) {
fn to_bary(&self, vertices: &Vec<Point3<f32>>, point: Point3<f32>) -> (f32, f32, f32) {
let t = tri_area(self.vertex2(vertices), self.vertex3(vertices), &point) / self.area;
let u = tri_area(self.vertex1(vertices), self.vertex3(vertices), &point) / self.area;
let v = tri_area(self.vertex1(vertices), self.vertex2(vertices), &point) / self.area;
@ -50,7 +50,7 @@ impl Triangle {
(t, u, v)
}
fn intersect_(&self, vertices: &Vec<Point3f>, ray: Ray) -> Option<(f32, f32, f32)> {
fn intersect_(&self, vertices: &Vec<Point3<f32>>, ray: Ray) -> Option<(f32, f32, f32)> {
let vect2_1 = self.vertex2(vertices) - self.vertex1(vertices);
let vect3_1 = self.vertex3(vertices) - self.vertex1(vertices);
@ -74,11 +74,11 @@ impl Triangle {
Some((t, u, v))
}
fn intersect(&self, vertices: &Vec<Point3f>, ray: Ray) -> Option<f32> {
fn intersect(&self, vertices: &Vec<Point3<f32>>, ray: Ray) -> Option<f32> {
self.intersect_(vertices, ray).map(|(t, u, v)| distance(&ray.origin, &self.from_bary(vertices, t, u, v)))
}
fn get_texture(&self, vertices: &Vec<Point3f>, point: Point3f) -> Texture {
fn getcolor(&self, vertices: &Vec<Point3<f32>>, point: Point3<f32>) -> Color {
let (t, u, v) = self.to_bary(vertices, point);
(*self.texture)(t, u, v)
}
@ -86,43 +86,48 @@ impl Triangle {
#[allow(dead_code)]
impl TriangleMesh {
pub fn new(vertices: Vec<Point3f>, tris: Vec<(usize, usize, usize, Box<dyn Fn(f32, f32, f32) -> Texture>)>) -> Self {
pub fn new(vertices: Vec<Point3<f32>>, tris: Vec<(usize, usize, usize, Box<dyn Fn(f32, f32, f32) -> Color>)>) -> Self {
let triangles = tris.into_iter()
.map(|(v1, v2, v3, f)| Triangle {
v1, v2, v3,
v1: v1,
v2: v2,
v3: v3,
normal: Unit::new_normalize((&vertices[v2] - &vertices[v1]).cross(&(&vertices[v3] - &vertices[v1]))),
area: tri_area(&vertices[v1], &vertices[v2], &vertices[v3]),
texture: f
}).collect();
TriangleMesh {
vertices, triangles
vertices: vertices,
tris: triangles
}
}
pub fn new_solid(vertices: Vec<Point3f>, tris: Vec<(usize, usize, usize)>, texture: Texture) -> Self {
pub fn new_solid(vertices: Vec<Point3<f32>>, tris: Vec<(usize, usize, usize)>, color: Color) -> Self {
let triangles = tris.into_iter()
.map(|(v1, v2, v3)| Triangle {
v1, v2, v3,
v1: v1,
v2: v2,
v3: v3,
normal: Unit::new_normalize((&vertices[v2] - &vertices[v1]).cross(&(&vertices[v3] - &vertices[v1]))),
area: tri_area(&vertices[v1], &vertices[v2], &vertices[v3]),
texture: Box::new(move |_, _, _| texture)
texture: Box::new(move |_, _, _| color)
}).collect();
TriangleMesh {
vertices,
triangles
vertices: vertices,
tris: triangles
}
}
pub fn singleton<F: 'static>(vertex1: Point3f, vertex2: Point3f, vertex3: Point3f, texture: F) -> Self
where F: Fn(f32, f32, f32) -> Texture
pub fn singleton<F: 'static>(vertex1: Point3<f32>, vertex2: Point3<f32>, vertex3: Point3<f32>, texture: F) -> Self
where F: Fn(f32, f32, f32) -> Color
{ TriangleMesh::new(vec![vertex1, vertex2, vertex3], vec![(0, 1, 2, Box::new(texture))]) }
pub fn singleton_solid(vertex1: Point3f, vertex2: Point3f, vertex3: Point3f, texture: Texture) -> Self
{ TriangleMesh::singleton(vertex1, vertex2, vertex3, move |_, _, _| texture) }
pub fn singleton_solid(vertex1: Point3<f32>, vertex2: Point3<f32>, vertex3: Point3<f32>, color: Color) -> Self
{ TriangleMesh::singleton(vertex1, vertex2, vertex3, move |_, _, _| color) }
fn closest_tri(&self, point: Point3f) -> &Triangle {
self.triangles.iter()
fn closest_tri(&self, point: Point3<f32>) -> &Triangle {
self.tris.iter()
.map(move |tri| {
let rel_pos = point - tri.vertex1(&self.vertices);
@ -145,46 +150,22 @@ impl TriangleMesh {
impl Surface for TriangleMesh {
fn intersect(&self, ray: Ray) -> Option<f32> {
self.triangles.iter()
self.tris.iter()
.filter_map(|tri| tri.intersect(&self.vertices, ray))
.min_by(|a, b| a.partial_cmp(&b).unwrap_or(Ordering::Equal))
}
fn normal(&self, point: Point3f) -> Unit3f {
fn normal(&self, point: Point3<f32>) -> Unit<Vector3<f32>> {
self.closest_tri(point).normal
}
fn get_texture(&self, point: Point3f) -> Texture {
self.closest_tri(point).get_texture(&self.vertices, point)
fn getcolor(&self, point: Point3<f32>) -> Color {
self.closest_tri(point).getcolor(&self.vertices, point)
}
// Uses Welzl's algorithm to solve the bounding sphere problem
fn bound(&self) -> Bound {
fn smallest_sphere_plane(points: Vec<&Point3f>, boundary: Vec<&Point3f>) -> (Point3f, f32) {
if points.len() == 0 || boundary.len() == 3 {
match boundary.len() {
0 => (Point3::new(0.0, 0.0, 0.0), 0.0),
1 => (*boundary[0], 0.0),
2 => { let half_span = 0.5 * (boundary[1] - boundary[0]);
(*boundary[0] + half_span, half_span.norm()) },
3 => triangle_sphere(boundary[0], boundary[1], boundary[2]),
_ => unreachable!()
}
} else {
let removed = points[0];
let points = Vec::from(&points[1..]);
let bound = smallest_sphere(points.clone(), boundary.clone());
if distance(&bound.0, removed) < bound.1 { return bound; }
let mut boundary = boundary.clone();
boundary.push(removed);
smallest_sphere_plane(points, boundary)
}
}
fn triangle_sphere(point1: &Point3f, point2: &Point3f, point3: &Point3f) -> (Point3f, f32) {
fn triangle_sphere(point1: &Point3<f32>, point2: &Point3<f32>, point3: &Point3<f32>) -> (Point3<f32>, f32) {
let a = point3 - point1;
let b = point2 - point1;
@ -198,15 +179,13 @@ impl Surface for TriangleMesh {
(point1 + to_center, radius)
}
fn tetrahedron_sphere(point1: &Point3f, point2: &Point3f, point3: &Point3f, point4: &Point3f) -> (Point3f, f32) {
fn tetrahedron_sphere(point1: &Point3<f32>, point2: &Point3<f32>, point3: &Point3<f32>, point4: &Point3<f32>) -> (Point3<f32>, f32) {
let matrix = Matrix4::from_rows(&[point1.to_homogeneous().transpose(),
point2.to_homogeneous().transpose(),
point3.to_homogeneous().transpose(),
point4.to_homogeneous().transpose()]);
let a = matrix.determinant() * 2.0;
if (a != 0.0) {
let mut matrix_mut = matrix.clone();
let squares = Vector4::new(point1.coords.norm_squared(), point2.coords.norm_squared(), point3.coords.norm_squared(), point4.coords.norm_squared());
@ -223,15 +202,9 @@ impl Surface for TriangleMesh {
let radius = distance(point1, &center);
(center, radius)
} else {
let points = vec![point1, point2, point3, point4];
let boundary = Vec::new();
smallest_sphere_plane(points, boundary)
}
}
fn smallest_sphere(points: Vec<&Point3f>, boundary: Vec<&Point3f>) -> (Point3f, f32) {
fn smallest_sphere(points: Vec<&Point3<f32>>, boundary: Vec<&Point3<f32>>) -> (Point3<f32>, f32) {
if points.len() == 0 || boundary.len() == 4 {
match boundary.len() {
0 => (Point3::new(0.0, 0.0, 0.0), 0.0),
@ -260,11 +233,110 @@ impl Surface for TriangleMesh {
use rand::thread_rng;
use rand::seq::SliceRandom;
let mut points: Vec<&Point3f> = self.vertices.iter().collect();
let mut points: Vec<&Point3<f32>> = self.vertices.iter().collect();
points.shuffle(&mut thread_rng());
let (center, radius) = smallest_sphere(points, Vec::new());
Bound { center, radius: radius + 1e-3, bypass: false }
Bound { center: center, radius: radius + 1e-3, bypass: false }
}
}
#[cfg(test)]
mod tests {
use super::*;
fn roundcolor(color: Color) -> Color {
Color::new((color.red * 100.0).round() / 100.0, (color.green * 100.0).round() / 100.0, (color.blue * 100.0).round() / 100.0)
}
#[test]
fn triangle_intersect() {
let triangle = TriangleMesh::singleton_solid(Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 1.0, 0.0), Point3::new(0.0, 0.0, 1.0), Color::black());
let ray = Ray::new(Point3::new(0.5, 5.0, 0.3), Vector3::new(0.0, -1.0, 0.0));
let (t, u, v) = triangle.tris[0].intersect_(&triangle.vertices, ray).unwrap();
println!("{},{},{}", t, u, v);
assert!(t >= 0.0 && t <= 1.0);
assert!(u >= 0.0 && u <= 1.0);
assert!(v >= 0.0 && v <= 1.0);
}
#[test]
fn triangle_getcolor() {
let triangle = TriangleMesh::singleton(Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 1.0, 0.0), Point3::new(0.0, 0.0, 1.0), |t, u, v| Color::new(t, u, v));
let t = 0.4;
let u = 0.1;
let v = 1.0 - t - u;
let point = triangle.tris[0].from_bary(&triangle.vertices, t, u, v);
assert_eq!(roundcolor(triangle.getcolor(point)), roundcolor(Color::new(t, u, v)));
}
#[test]
fn triangle_bounds() {
let point1 = Point3::new(0.0, 0.0, 0.0);
let point2 = Point3::new(1.0, 0.0, 0.0);
let point3 = Point3::new(0.0, 1.0, 0.0);
let triangle = TriangleMesh::singleton_solid(point1, point2, point3, Color::black());
let bound = triangle.bound();
println!("{:?}", bound);
assert!(bound.contains(&point1));
assert!(bound.contains(&point2));
assert!(bound.contains(&point3));
}
/*
#[test]
fn triangle_tobound() {
let point1 = Point3::new(-3.0, 4.0, -6.0);
let point2 = Point3::new(5.0, -2.0, -7.0);
let point3 = Point3::new(9.0, -7.0, 3.0);
let (center, radius) = triangle_sphere(&point1, &point2, &point3);
let bound = Bound { center: center, radius: radius + 0.01, bypass: false };
println!("{:?}", bound);
println!("{}\n{}\n{}", distance(&bound.center, &point1),
distance(&bound.center, &point2),
distance(&bound.center, &point3));
assert!(bound.contains(&point1));
assert!(bound.contains(&point2));
assert!(bound.contains(&point3));
}
#[test]
fn triangle_tetrabound() {
let point1 = Point3::new(8.0, -2.0, -5.0);
let point2 = Point3::new(-3.0, 4.0, -6.0);
let point3 = Point3::new(-3.0, -9.0, 3.0);
let point4 = Point3::new(-6.0, 5.0, -9.0);
let (center, radius) = tetrahedron_sphere(&point1, &point2, &point3, &point4);
let bound = Bound { center: center, radius: radius + 0.01, bypass: false };
println!("{:?}", bound);
println!("{}\n{}\n{}\n{}", distance(&bound.center, &point1),
distance(&bound.center, &point2),
distance(&bound.center, &point3),
distance(&bound.center, &point4));
assert!(bound.contains(&point1));
assert!(bound.contains(&point2));
assert!(bound.contains(&point3));
assert!(bound.contains(&point4));
}
*/
}

View file

@ -1,13 +1,12 @@
extern crate nalgebra as na;
use std::cmp::Ordering;
use std::f32::consts::PI;
use na::*;
use na::geometry::Point3;
use crate::object::*;
use crate::types::*;
use crate::object::*;
fn trace(ray: Ray, objects: &Vec<Object>) -> Option<(&Object, f32)> {
objects.iter()
@ -16,24 +15,11 @@ fn trace(ray: Ray, objects: &Vec<Object>) -> Option<(&Object, f32)> {
.min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal))
}
fn light_point(objects: &Vec<Object>, obj: &Object, point: Point3f, light: &dyn Light) -> Color {
if light.check_shadow(point, objects) {
let texture = obj.get_texture(point);
light.get_color(point) * (texture.albedo / PI) * light.intensity(point) * obj.normal(point).dot(&*light.direction(point))
} else {
// Point is in shadow
Color::black()
}
}
pub fn cast_ray(ray: Ray, scene: &Scene) -> Color {
if let Some((obj, dist)) = trace(ray, &scene.objects) {
let point = ray.project(dist);
let surface_color = obj.get_texture(point).color;
scene.lights.iter()
.map(|light| light_point(&scene.objects, obj, point, &**light))
.fold(Color::black(), |acc, c| acc + c) * surface_color
} else { scene.background }
obj.getcolor(point)
}
else { scene.background }
}

View file

@ -5,24 +5,23 @@ use std::ops::{Add, Mul};
use na::*;
use na::geometry::Point3;
pub type Point3f = Point3<f32>;
pub type Vector3f = Vector3<f32>;
pub type Unit3f = Unit<Vector3<f32>>;
#[derive(Clone, Copy, Debug)]
pub struct Ray {
pub origin: Point3f,
pub direction: Unit3f
pub origin: Point3<f32>,
pub direction: Unit<Vector3<f32>>
}
impl Ray {
pub fn from_parts(origin: Point3f, direction: Unit3f) -> Self {
Ray { origin, direction }
pub fn from_parts(origin: Point3<f32>, direction: Unit<Vector3<f32>>) -> Self {
Ray {
origin: origin,
direction: direction
}
pub fn new(origin: Point3f, direction: Vector3f) -> Self { Ray::from_parts(origin, Unit::new_normalize(direction)) }
pub fn from_points(origin: Point3f, points_to: Point3f) -> Self { Ray::new(origin, points_to - origin) }
}
pub fn new(origin: Point3<f32>, direction: Vector3<f32>) -> Self { Ray::from_parts(origin, Unit::new_normalize(direction)) }
pub fn from_points(origin: Point3<f32>, points_to: Point3<f32>) -> Self { Ray::new(origin, points_to - origin) }
pub fn project(&self, t: f32) -> Point3f { self.origin + t * self.direction.into_inner() }
pub fn project(&self, t: f32) -> Point3<f32> { self.origin + t * self.direction.into_inner() }
}
#[derive(Clone, Copy, Debug, PartialEq)]
@ -94,19 +93,3 @@ impl Mul<f32> for Color {
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Texture {
pub color: Color,
pub albedo: f32
}
#[allow(dead_code)]
impl Texture {
pub fn new(red: f32, green: f32, blue: f32, albedo: f32) -> Self {
Texture {
color: Color::new(red, green, blue),
albedo
}
}
}