Commit e6c0cd69 authored by Stephen D's avatar Stephen D
Browse files

next event estimation - not working

parent 6bbc3b1c
......@@ -16,4 +16,4 @@ image = "0.23"
rand = "0.8"
rayon = "1.5"
dyn-clonable = "0.9.0"
regex = "1"
\ No newline at end of file
regex = "1"
tab_spaces = 4
hard_tabs = true
edition = "2018"
\ No newline at end of file
use crate::*;
pub struct HitRecord<'a> {
pub p: Point3,
pub normal: Vec3,
pub material: &'a dyn Material,
pub t: f64,
pub u: f64,
pub v: f64,
pub front_face: bool,
}
impl<'a> HitRecord<'a> {
pub fn new(
p: Point3,
outward_normal: Vec3,
material: &'a dyn Material,
t: f64,
u: f64,
v: f64,
ray: &Ray,
) -> Self {
let front_face = Vec3::dot(ray.direction(), outward_normal) < 0.0;
let normal = if front_face {
outward_normal
} else {
-outward_normal
};
Self {
p,
normal,
material,
t,
u,
v,
front_face,
}
}
}
#[clonable]
pub trait Hittable: Send + Sync + Clone {
fn hit(&self, r: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord>;
fn bounding_box(&self) -> Aabb;
fn pdf_value(&self, _o: Point3, _v: Vec3) -> f64 {
0.0
}
fn random(&self, _rng: &mut ThreadRng, _o: Vec3) -> Vec3 {
Vec3::new(1.0, 0.0, 0.0)
}
fn emits(&self) -> bool {
false
}
}
#[derive(Clone)]
pub struct Translate<'a> {
child: Box<dyn Hittable + 'a>,
offset: Vec3,
}
impl<'a> Translate<'a> {
pub fn new(child: Box<dyn Hittable + 'a>, offset: Vec3) -> Self {
Self { child, offset }
}
}
impl<'a> Hittable for Translate<'a> {
fn hit(&self, r: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
let moved_r = Ray::new(r.origin() - self.offset, r.direction());
let rec = self.child.hit(&moved_r, t_min, t_max)?;
let rec = HitRecord::new(
rec.p + self.offset,
rec.normal,
rec.material,
rec.t,
rec.u,
rec.v,
&moved_r,
);
Some(rec)
}
fn bounding_box(&self) -> Aabb {
let res = self.child.bounding_box();
Aabb::new(res.min() + self.offset, res.max() + self.offset)
}
}
#[derive(Clone)]
pub struct RotateX<'a> {
child: Box<dyn Hittable + 'a>,
sin_theta: f64,
cos_theta: f64,
bbox: Aabb,
}
impl<'a> RotateX<'a> {
pub fn new(child: Box<dyn Hittable + 'a>, angle: f64) -> Self {
let radians = angle.to_radians();
let sin_theta = radians.sin();
let cos_theta = radians.cos();
let bbox = child.bounding_box();
let mut min = Point3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
let mut max = Point3::new(-f64::INFINITY, -f64::INFINITY, -f64::INFINITY);
for i in 0..2 {
for j in 0..2 {
for k in 0..2 {
let i = i as f64;
let j = j as f64;
let k = k as f64;
let x = i * bbox.max().x() + (1.0 - i) * bbox.min().x();
let y = j * bbox.max().y() + (1.0 - j) * bbox.min().y();
let z = k * bbox.max().z() + (1.0 - k) * bbox.min().z();
let new_y = cos_theta * y + sin_theta * z;
let new_z = -sin_theta * y + cos_theta * z;
let tester = Vec3::new(x, new_y, new_z);
for c in 0..3 {
min[c] = f64::min(min[c], tester[c]);
max[c] = f64::max(max[c], tester[c]);
}
}
}
}
let bbox = Aabb::new(min, max);
Self {
child,
sin_theta,
cos_theta,
bbox,
}
}
}
impl<'a> Hittable for RotateX<'a> {
fn hit(&self, r: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
let mut origin = r.origin();
let mut direction = r.direction();
origin[1] = self.cos_theta * r.origin()[1] - self.sin_theta * r.origin()[2];
origin[2] = self.sin_theta * r.origin()[1] + self.cos_theta * r.origin()[2];
direction[1] = self.cos_theta * r.direction()[1] - self.sin_theta * r.direction()[2];
direction[2] = self.sin_theta * r.direction()[1] + self.cos_theta * r.direction()[2];
let rotated_r = Ray::new(origin, direction);
let rec = self.child.hit(&rotated_r, t_min, t_max)?;
let mut p = rec.p;
let mut normal = rec.normal;
p[1] = self.cos_theta * rec.p[1] + self.sin_theta * rec.p[2];
p[2] = -self.sin_theta * rec.p[1] + self.cos_theta * rec.p[2];
normal[1] = self.cos_theta * rec.normal[1] + self.sin_theta * rec.normal[2];
normal[2] = -self.sin_theta * rec.normal[1] + self.cos_theta * rec.normal[2];
Some(HitRecord::new(
p,
normal,
rec.material,
rec.t,
rec.u,
rec.v,
&rotated_r,
))
}
fn bounding_box(&self) -> Aabb {
self.bbox
}
}
#[derive(Clone)]
pub struct RotateY<'a> {
child: Box<dyn Hittable + 'a>,
sin_theta: f64,
cos_theta: f64,
bbox: Aabb,
}
impl<'a> RotateY<'a> {
pub fn new(child: Box<dyn Hittable + 'a>, angle: f64) -> Self {
let radians = angle.to_radians();
let sin_theta = radians.sin();
let cos_theta = radians.cos();
let bbox = child.bounding_box();
let mut min = Point3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
let mut max = Point3::new(-f64::INFINITY, -f64::INFINITY, -f64::INFINITY);
for i in 0..2 {
for j in 0..2 {
for k in 0..2 {
let i = i as f64;
let j = j as f64;
let k = k as f64;
let x = i * bbox.max().x() + (1.0 - i) * bbox.min().x();
let y = j * bbox.max().y() + (1.0 - j) * bbox.min().y();
let z = k * bbox.max().z() + (1.0 - k) * bbox.min().z();
let new_x = cos_theta * x + sin_theta * z;
let new_z = -sin_theta * x + cos_theta * z;
let tester = Vec3::new(new_x, y, new_z);
for c in 0..3 {
min[c] = f64::min(min[c], tester[c]);
max[c] = f64::max(max[c], tester[c]);
}
}
}
}
let bbox = Aabb::new(min, max);
Self {
child,
sin_theta,
cos_theta,
bbox,
}
}
}
impl<'a> Hittable for RotateY<'a> {
fn hit(&self, r: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
let mut origin = r.origin();
let mut direction = r.direction();
origin[0] = self.cos_theta * r.origin()[0] - self.sin_theta * r.origin()[2];
origin[2] = self.sin_theta * r.origin()[0] + self.cos_theta * r.origin()[2];
direction[0] = self.cos_theta * r.direction()[0] - self.sin_theta * r.direction()[2];
direction[2] = self.sin_theta * r.direction()[0] + self.cos_theta * r.direction()[2];
let rotated_r = Ray::new(origin, direction);
let rec = self.child.hit(&rotated_r, t_min, t_max)?;
let mut p = rec.p;
let mut normal = rec.normal;
p[0] = self.cos_theta * rec.p[0] + self.sin_theta * rec.p[2];
p[2] = -self.sin_theta * rec.p[0] + self.cos_theta * rec.p[2];
normal[0] = self.cos_theta * rec.normal[0] + self.sin_theta * rec.normal[2];
normal[2] = -self.sin_theta * rec.normal[0] + self.cos_theta * rec.normal[2];
Some(HitRecord::new(
p,
normal,
rec.material,
rec.t,
rec.u,
rec.v,
&rotated_r,
))
}
fn bounding_box(&self) -> Aabb {
self.bbox
}
}
......@@ -8,10 +8,19 @@ pub struct XyRect<'a> {
y0: f64,
y1: f64,
k: f64,
reverse: bool,
}
impl<'a> XyRect<'a> {
pub fn new(x0: f64, x1: f64, y0: f64, y1: f64, k: f64, mp: &'a dyn Material) -> Self {
pub fn new(
x0: f64,
x1: f64,
y0: f64,
y1: f64,
k: f64,
mp: &'a dyn Material,
reverse: bool,
) -> Self {
Self {
x0,
x1,
......@@ -19,6 +28,7 @@ impl<'a> XyRect<'a> {
y1,
k,
mp,
reverse,
}
}
}
......@@ -37,11 +47,20 @@ impl<'a> Hittable for XyRect<'a> {
return None;
}
let u = (x - self.x0) / (self.x1 - self.x0);
let mut u = (x - self.x0) / (self.x1 - self.x0);
let v = (y - self.y0) / (self.y1 - self.y0);
if self.reverse {
u = 1.0 - u;
}
let outward_normal = Vec3::new(0.0, 0.0, 1.0);
let p = r.at(t);
if !self.mp.hit(u, v, p) {
return None;
}
Some(HitRecord::new(p, outward_normal, self.mp, t, u, v, r))
}
......@@ -95,6 +114,10 @@ impl<'a> Hittable for XzRect<'a> {
let outward_normal = Vec3::new(0.0, 1.0, 0.0);
let p = r.at(t);
if !self.mp.hit(u, v, p) {
return None;
}
Some(HitRecord::new(p, outward_normal, self.mp, t, u, v, r))
}
......@@ -104,6 +127,29 @@ impl<'a> Hittable for XzRect<'a> {
Point3::new(self.x1, self.k + 0.0001, self.z1),
)
}
fn pdf_value(&self, o: Point3, v: Vec3) -> f64 {
let rec = match self.hit(&Ray::new(o, v), 0.001, f64::INFINITY) {
Some(x) => x,
None => return 0.0,
};
let area = (self.x1 - self.x0) * (self.z1 - self.z0);
let dist_squared = rec.t * rec.t * v.length_squared();
let cosine = (Vec3::dot(v, rec.normal) / v.length()).abs();
dist_squared / (cosine * area)
}
fn random(&self, rng: &mut ThreadRng, o: Vec3) -> Vec3 {
let random_pt = Point3::new(
rng.gen_range(self.x0..self.x1),
self.k,
rng.gen_range(self.z0..self.z1),
);
random_pt - o
}
}
#[derive(Clone)]
pub struct YzRect<'a> {
......@@ -113,10 +159,19 @@ pub struct YzRect<'a> {
z0: f64,
z1: f64,
k: f64,
reverse: bool,
}
impl<'a> YzRect<'a> {
pub fn new(y0: f64, y1: f64, z0: f64, z1: f64, k: f64, mp: &'a dyn Material) -> Self {
pub fn new(
y0: f64,
y1: f64,
z0: f64,
z1: f64,
k: f64,
mp: &'a dyn Material,
reverse: bool,
) -> Self {
Self {
y0,
y1,
......@@ -124,6 +179,7 @@ impl<'a> YzRect<'a> {
z1,
k,
mp,
reverse,
}
}
}
......@@ -142,11 +198,20 @@ impl<'a> Hittable for YzRect<'a> {
return None;
}
let mut u = (z - self.z0) / (self.z1 - self.z0);
let v = (y - self.y0) / (self.y1 - self.y0);
let u = (z - self.z0) / (self.z1 - self.z0);
if self.reverse {
u = 1.0 - u;
}
let outward_normal = Vec3::new(1.0, 0.0, 0.0);
let p = r.at(t);
if !self.mp.hit(u, v, p) {
return None;
}
Some(HitRecord::new(p, outward_normal, self.mp, t, u, v, r))
}
......
......@@ -6,6 +6,8 @@ pub struct Block<'a> {
max: Point3,
sides: HittableList<'a>,
aabb: Aabb,
emits: bool,
volume: f64,
}
// TODO need to determine which side is the front somehow
......@@ -20,6 +22,7 @@ impl<'a> Block<'a> {
p1.y(),
p1.z(),
mat.side.as_ref(),
false,
)));
sides.add(Box::new(XyRect::new(
......@@ -29,6 +32,7 @@ impl<'a> Block<'a> {
p1.y(),
p0.z(),
mat.front.as_ref(), // arbitrary
true,
)));
sides.add(Box::new(XzRect::new(
......@@ -56,6 +60,7 @@ impl<'a> Block<'a> {
p1.z(),
p1.x(),
mat.side.as_ref(),
true,
)));
sides.add(Box::new(YzRect::new(
......@@ -65,27 +70,91 @@ impl<'a> Block<'a> {
p1.z(),
p0.x(),
mat.side.as_ref(),
false,
)));
let volume = (p1.x() - p0.x()) * (p1.y() - p0.y()) * (p1.z() - p0.z());
Self {
min: p0,
max: p1,
sides,
aabb: Aabb::new(p0, p1),
emits: mat.front.emits(),
volume,
}
}
}
impl<'a> Hittable for Block<'a> {
#[inline(always)]
fn hit(&self, r: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
if !self.aabb.hit(r, t_min, t_max) {
return None;
}
self.sides.hit(r, t_min, t_max)
let hit = self.sides.hit(r, t_min, t_max)?;
Some(hit)
}
fn bounding_box(&self) -> Aabb {
self.aabb
}
fn emits(&self) -> bool {
self.emits
}
fn pdf_value(&self, o: Point3, v: Vec3) -> f64 {
let rec = match self.hit(&Ray::new(o, v), 0.001, f64::INFINITY) {
Some(x) => x,
None => return 0.0,
};
let dist_squared = rec.t * rec.t * v.length_squared();
let cosine = (Vec3::dot(v, rec.normal) / v.length()).abs();
dist_squared / (cosine * self.volume)
}
fn random(&self, rng: &mut ThreadRng, o: Vec3) -> Vec3 {
let random_pt = Point3::new(
rng.gen_range(self.min.x()..self.max.x()),
rng.gen_range(self.min.y()..self.max.y()),
rng.gen_range(self.min.z()..self.max.z()),
);
random_pt - o
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_block_randomness() {
let p0 = Point3::new(5.0, 5.0, 5.0);
let p1 = Point3::new(6.0, 6.0, 6.0);
let texture = BlockTexture::new(
"textures/block/",
&fastanvil::Block {
name: "minecraft:glowstone".to_string(),
properties: HashMap::new(),
},
)
.unwrap();
let block = Block::new(p0, p1, &texture);
let mut rng = thread_rng();
for _ in 0..10_000 {
let origin = Point3::new(-2.0, 3.0, 11.0);
let dir = block.random(&mut rng, origin);
assert!(block.pdf_value(origin, dir).abs() > 0.00001);
}
}
}
......@@ -7,28 +7,47 @@ pub struct BlockTexture {
pub front: Box<dyn Material>,
pub side: Box<dyn Material>,
pub bottom: Box<dyn Material>,
pub emit: bool,
}
impl BlockTexture {
// TODO support more than PNGs
pub fn new(dir: &str, name: &str) -> Option<Self> {
pub fn new(dir: &str, block: &fastanvil::Block) -> Option<Self> {
let name = &block.name[10..];
let name = if name == "wall_torch" { "torch" } else { name };
let emit = ["torch", "glowstone", "jack_o_lantern"].contains(&name);
let transparent = ["glass", "glass_pane"].contains(&name);
let dir = Path::new(dir);
if name == "wheat" {
let age: u8 = block.properties.get("age")?.parse().ok()?;
let filename = dir.join(format!("wheat_stage{}.png", age));
let texture = ImageTexture::new(&filename)?;
return Some(Self {
top: Box::new(Lambertian::new(Box::new(texture.clone()))),
front: Box::new(Lambertian::new(Box::new(texture.clone()))),
side: Box::new(Lambertian::new(Box::new(texture.clone()))),
bottom: Box::new(Lambertian::new(Box::new(texture.clone()))),
emit: false,
});
}
let mut top = None;
let mut front = None;
let mut side = None;
let mut bottom = None;
if let Some(sides) = ImageTexture::new(&dir.join(format!("{}{}", name, ".png"))) {
if let Some(sides) = ImageTexture::new(&dir.join(format!("{}.png", name))) {
top = Some(sides.clone());
front = Some(sides.clone());
side = Some(sides.clone());
bottom = Some(sides);
}
if let Some(mut new_top) = ImageTexture::new(&dir.join(format!("{}{}", name, "_top.png"))) {
if let Some(mut new_top) = ImageTexture::new(&dir.join(format!("{}_top.png", name))) {
if name == "grass_block" {
new_top.tint(Color::new(0.533333, 0.733333, 0.4039));
}
......@@ -36,27 +55,33 @@ impl BlockTexture {
top = Some(new_top);
}
if let Some(new_side) = ImageTexture::new(&dir.join(format!("{}{}", name, "_side.png"))) {
if let Some(new_side) = ImageTexture::new(&dir.join(format!("{}_side.png", name))) {
side = Some(new_side.clone());
front = Some(new_side.clone());
bottom = Some(new_side);
}
if let Some(new_bottom) = ImageTexture::new(&dir.join(format!("{}{}", name, "_bottom.png")))
{
if let Some(new_bottom) = ImageTexture::new(&dir.join(format!("{}_bottom.png", name))) {
bottom = Some(new_bottom);
}
// block-specific exceptions
if name == "grass_block" {
if let Some(new_bottom) = ImageTexture::new(&dir.join("dirt.png")) {
bottom = Some(new_bottom);
match name {
"grass_block" => {
if let Some(new_bottom) = ImageTexture::new(&dir.join("dirt.png")) {
bottom = Some(new_bottom);
}
}