Browse Source

next event estimation - not working

master
Stephen 2 weeks ago
parent
commit
e6c0cd69d0
18 changed files with 1081 additions and 295 deletions
  1. +1
    -1
      Cargo.toml
  2. +3
    -0
      rustfmt.toml
  3. +280
    -0
      src/#hittable.rs#
  4. +69
    -4
      src/aarect.rs
  5. +70
    -1
      src/block.rs
  6. +48
    -13
      src/block_texture.rs
  7. +0
    -98
      src/constant_medium.rs
  8. +12
    -0
      src/hittable.rs
  9. +12
    -0
      src/hittable_list.rs
  10. +77
    -152
      src/main.rs
  11. +47
    -11
      src/material.rs
  12. +152
    -0
      src/mc_blocks.rs
  13. +19
    -0
      src/mc_world.rs
  14. +37
    -0
      src/onb.rs
  15. +80
    -0
      src/pdf.rs
  16. +119
    -0
      src/scene.rs
  17. +31
    -12
      src/texture.rs
  18. +24
    -3
      src/vector.rs

+ 1
- 1
Cargo.toml View File

@ -16,4 +16,4 @@ image = "0.23"
rand = "0.8"
rayon = "1.5"
dyn-clonable = "0.9.0"
regex = "1"
regex = "1"

+ 3
- 0
rustfmt.toml View File

@ -0,0 +1,3 @@
tab_spaces = 4
hard_tabs = true
edition = "2018"

+ 280
- 0
src/#hittable.rs# View File

@ -0,0 +1,280 @@
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
}
}

+ 69
- 4
src/aarect.rs View File

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


+ 70
- 1
src/block.rs View File

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

+ 48
- 13
src/block_texture.rs View File

@ -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);
}
}
}
if name == "tnt" {
println!("{:?} {:?} {:?} {:?}", top, front, side, bottom);
"jack_o_lantern" => {
// TODO do sides as well
if let Some(new_top) = ImageTexture::new(&dir.join("pumpkin_top.png")) {
top = Some(new_top);
}
}
_ => {}
}
let (top, front, side, bottom): (
@ -71,7 +96,16 @@ impl BlockTexture {
Box::new(DiffuseLight::new(Box::new(side?))),
Box::new(DiffuseLight::new(Box::new(bottom?))),
)
} else {
}
/*else if transparent {
(
Box::new(Dielectric::new(Box::new(top?), 1.1)),
Box::new(Dielectric::new(Box::new(front?), 1.1)),
Box::new(Dielectric::new(Box::new(side?), 1.1)),
Box::new(Dielectric::new(Box::new(bottom?), 1.1)),
)
}*/
else {
(
Box::new(Lambertian::new(Box::new(top?))),
Box::new(Lambertian::new(Box::new(front?))),
@ -85,6 +119,7 @@ impl BlockTexture {
front,
side,
bottom,
emit,
})
}
}

+ 0
- 98
src/constant_medium.rs View File

@ -1,98 +0,0 @@
use crate::*;
#[derive(Clone)]
pub struct ConstantMedium<'a> {
boundary: Box<dyn Hittable + 'a>,
phase_function: Box<dyn Material>,
neg_inv_density: f64,
}
impl<'a> ConstantMedium<'a> {
pub fn new(boundary: Box<dyn Hittable + 'a>, d: f64, texture: Box<dyn Texture>) -> Self {
Self {
boundary,
neg_inv_density: -1.0 / d,
phase_function: Box::new(Isotropic::new(texture)),
}
}
pub fn from_color(boundary: Box<dyn Hittable + 'a>, d: f64, c: Color) -> Self {
Self {
boundary,
neg_inv_density: -1.0 / d,
phase_function: Box::new(Isotropic::from_color(c)),
}
}
}
impl<'a> Hittable for ConstantMedium<'a> {
fn hit(&self, r: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
let mut rec1 = self.boundary.hit(r, -f64::INFINITY, f64::INFINITY)?;
let mut rec2 = self.boundary.hit(r, rec1.t + 0.0001, f64::INFINITY)?;
if rec1.t < t_min {
rec1.t = t_min;
}
if rec2.t > t_max {
rec2.t = t_max;
}
if rec1.t >= rec2.t {
return None;
}
if rec1.t < 0.0 {
rec1.t = 0.0;
}
let ray_length = r.direction().length();
let dist_inside_boundary = (rec2.t - rec1.t) * ray_length;
let hit_distance = self.neg_inv_density * thread_rng().gen::<f64>().ln();
if hit_distance > dist_inside_boundary {
return None;
}
let t = rec1.t + hit_distance / ray_length;
let p = r.at(t);
Some(HitRecord {
p,
normal: Vec3::new(1.0, 0.0, 0.0), // arbitrary
material: self.phase_function.as_ref(),
t,
u: 0.0,
v: 0.0,
front_face: true,
})
}
fn bounding_box(&self) -> Aabb {
self.boundary.bounding_box()
}
}
#[derive(Clone)]
pub struct Isotropic {
albedo: Box<dyn Texture>,
}
impl Isotropic {
pub fn new(albedo: Box<dyn Texture>) -> Self {
Self { albedo }
}
pub fn from_color(color: Color) -> Self {
Self {
albedo: Box::new(SolidColor::new(color)),
}
}
}
impl Material for Isotropic {
fn scatter(&self, ray_in: &Ray, hit_record: &HitRecord) -> Option<ScatterData> {
let scattered = Ray::new(hit_record.p, Vec3::random_in_unit_sphere());
let attenuated = self.albedo.value(hit_record.u, hit_record.v, hit_record.p);
Some(ScatterData::new(attenuated, scattered))
}
}

+ 12
- 0
src/hittable.rs View File

@ -42,6 +42,18 @@ impl<'a> HitRecord<'a> {
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)]


+ 12
- 0
src/hittable_list.rs View File

@ -50,4 +50,16 @@ impl<'a> Hittable for HittableList<'a> {
output_box
}
fn pdf_value(&self, o: Point3, v: Vec3) -> f64 {
let weight = 1.0 / self.objects.len() as f64;
let sum: f64 = self.objects.iter().map(|obj| obj.pdf_value(o, v)).sum();
sum * weight
}
fn random(&self, rng: &mut ThreadRng, o: Vec3) -> Vec3 {
let size = self.objects.len();
self.objects[rng.gen_range(0..size)].random(rng, o)
}
}

+ 77
- 152
src/main.rs View File

@ -4,14 +4,17 @@ mod block;
mod block_texture;
mod bvh;
mod camera;
mod constant_medium;
mod hittable;
mod hittable_list;
mod image;
mod material;
mod mc_blocks;
mod mc_world;
mod onb;
mod pdf;
mod perlin;
mod ray;
mod scene;
mod sphere;
mod texture;
mod vector;
@ -22,18 +25,20 @@ use crate::block::*;
use crate::block_texture::*;
use crate::bvh::*;
use crate::camera::*;
use crate::constant_medium::*;
use crate::hittable::*;
use crate::hittable_list::*;
use crate::image::*;
use crate::material::*;
use crate::mc_blocks::*;
use crate::mc_world::*;
use crate::onb::*;
use crate::pdf::*;
use crate::perlin::*;
use crate::ray::*;
use crate::scene::*;
use crate::sphere::*;
use crate::texture::*;
use crate::vector::*;
use std::time::Instant;
use dyn_clonable::*;
use fastanvil::{Chunk, JavaChunk, RegionBuffer};
@ -42,6 +47,7 @@ use rand::prelude::*;
use rayon::prelude::*;
use std::collections::HashMap;
use std::fs;
use std::time::Instant;
fn main() {
let world = McWorld::new("world/region");
@ -50,183 +56,102 @@ fn main() {
let min_x = -318;
let max_x = -300;
let min_y = 50;
let max_y = 255;
let min_z = 120;
let max_z = 145;
// load textures
for x in min_x..max_x {
for y in 30..255 {
for z in min_z..max_z {
if let Some(block) = world.block(x, y, z) {
if block.name != "minecraft:air" {
if !textures.contains_key(&block.name) {
match BlockTexture::new("textures/block", &block.name[10..]) {
Some(texture) => {
textures.insert(block.name.clone(), texture);
}
None => {
eprintln!("Could not load texture for {}", &block.name[10..]);
}
};
}
world.for_each_block(
(min_x, min_y, min_z),
(max_x, max_y, max_z),
|_, _, _, block| {
if !textures.contains_key(&block.name) {
match BlockTexture::new("textures/block", &block) {
Some(texture) => {
// TODO also use block properties for key
textures.insert(block.name.clone(), texture);
}
}
None => {
eprintln!("Could not load texture for {}", &block.name[10..]);
}
};
}
}
}
},
);
let sun_mat = DiffuseLight::from_color(Color::new(100.0, 100.0, 100.0));
let mut objects = HittableList::new();
for x in min_x..max_x {
for y in 50..255 {
for z in min_z..max_z {
if let Some(block) = world.block(x, y, z) {
if block.name == "minecraft:torch" || block.name == "minecraft:wall_torch" {
println!("Found torch");
}
// TODO
if block.name != "minecraft:air"
&& block.name != "minecraft:fire"
&& block.name != "minecraft:redstone_wire"
&& block.name != "minecraft:lava"
&& block.name != "minecraft:water"
&& block.name != "minecraft:red_bed"
&& block.name != "minecraft:oak_wall_sign"
&& block.name != "minecraft:oak_sign"
&& block.name != "minecraft:stone_button"
&& block.name != "minecraft:smooth_stone_slab"
&& block.name != "minecraft:iron_door"
&& block.name != "minecraft:stone_pressure_plate"
&& block.name != "minecraft:stone_brick_slab"
&& block.name != "minecraft:oak_fence"
&& block.name != "minecraft:oak_fence_gate"
&& block.name != "minecraft:oak_stairs"
&& block.name != "minecraft:petrified_oak_slab"
&& block.name != "minecraft:chest"
&& block.name != "minecraft:piston_head"
&& block.name != "minecraft:sticky_piston"
&& block.name != "minecraft:dispenser"
&& block.name != "minecraft:oak_door"
&& block.name != "minecraft:nether_brick_fence"
&& block.name != "minecraft:redstone_wall_torch"
&& block.name != "minecraft:glass_pane"
&& block.name != "minecraft:wheat"
&& block.name != "minecraft:cracked_stone_bricks"
&& block.name != "minecraft:stone_bricks"
// && block.name != "minecraft:tnt"
{
let x = x as f64;
let y = y as f64;
let z = z as f64;
world.for_each_block(
(min_x, min_y, min_z),
(max_x, max_y, max_z),
|x, y, z, block| {
let x = x as f64;
let y = y as f64;
let z = z as f64;
match block.name.as_str() {
"minecraft:wheat" => objects.add(Box::new(Wheat::new(
Point3::new(x, y, z),
Point3::new(x + 1.0, y + 1.0, z + 1.0),
textures
.get(&block.name)
.unwrap_or_else(|| panic!("404 on {}", block.name))
.front
.as_ref(),
))),
"minecraft:sugar_cane" => objects.add(Box::new(SugarCane::new(
Point3::new(x, y, z),
Point3::new(x + 1.0, y + 1.0, z + 1.0),
textures
.get(&block.name)
.unwrap_or_else(|| panic!("404 on {}", block.name))
.front
.as_ref(),
))),
_ => {
if let Some(texture) = textures.get(&block.name) {
objects.add(Box::new(Block::new(
Point3::new(x, y, z),
Point3::new(x + 1.0, y + 1.0, z + 1.0),
&textures
.get(&block.name)
.unwrap_or_else(|| panic!("404 on {}", block.name)),
)));
&texture,
)))
}
}
}
}
}
};
},
);
/*objects.add(Box::new(Sphere::new(
Point3::new(100.0, 100.0, 100.0),
let sun = Box::new(Sphere::new(
Point3::new(-400.3, 150.0, 75.0),
10.0,
&sun_mat,
)));*/
let objects = Bvh::new(objects);
println!("Starting render");
let start = Instant::now();
// Image
let aspect_ratio: f64 = 16.0 / 9.0;
let img_width: usize = 1920;
let samples_per_pixel: usize = 1000;
const MAX_DEPTH: u32 = 5;
// Camera
));
let lookfrom = Vec3::new(-316.0, 84.0, 125.0);
let lookat = Vec3::new(-308.3, 73.7, 136.0);
objects.add(sun);
let vfov = 90.0;
let aperture = 0.0;
let background = Color::new(0.5, 0.9, 0.9);
// let background = Color::new(0.0, 0.0, 0.0);
let mut lights = HittableList::new();
let img_height: usize = (img_width as f64 / aspect_ratio) as usize;
let cam = Camera::new(
lookfrom,
lookat,
Vec3::new(0.0, 1.0, 0.0),
vfov,
aspect_ratio,
aperture,
10.0,
);
// Render
let framebuffer: Vec<_> = (0..(img_width * img_height))
.into_par_iter()
.map(|idx| {
let i = idx % img_width;
let j = idx / img_width;
let mut rng = rand::thread_rng();
let mut pixel_colour = Color::new(0.0, 0.0, 0.0);
for obj in &objects.objects {
if obj.emits() {
lights.add(obj.clone());
}
}
for _ in 0..samples_per_pixel {
let u = (i as f64 + rng.gen::<f64>()) / (img_width as f64 - 1.0);
let v = (j as f64 + rng.gen::<f64>()) / (img_height as f64 - 1.0);
let r = cam.get_ray(u, v);
pixel_colour += ray_colour(&r, background, &objects, MAX_DEPTH);
}
let objects = Bvh::new(objects);
pixel_colour
})
.collect();
let scene = Scene::new(objects, lights);
let mut img = Image::new(img_width, img_height);
println!("Starting render");
for (idx, colour) in framebuffer.iter().enumerate() {
let i = idx % img_width;
let j = idx / img_width;
let start = Instant::now();
img.write_color(i, j, colour, samples_per_pixel);
}
let img = scene.render();
println!("Render took {} seconds", start.elapsed().as_secs());
img.save("image.png");
}
fn ray_colour(ray: &Ray, background: Color, world: &dyn Hittable, depth: u32) -> Color {
if depth <= 0 {
return Color::new(0.0, 0.0, 0.0);
}
match world.hit(ray, 0.001, f64::INFINITY) {
Some(rec) => {
let emitted = rec.material.emitted(rec.u, rec.v, rec.p);
match rec.material.scatter(&ray, &rec) {
Some(data) => {
emitted
+ data.attenuation
* ray_colour(&data.scattered, background, world, depth - 1)
}
None => emitted,
}
}
None => background,
}
}

+ 47
- 11
src/material.rs View File

@ -4,13 +4,15 @@ use dyn_clonable::*;
pub struct ScatterData {
pub attenuation: Color,
pub scattered: Ray,
pub pdf: Box<dyn Pdf>,
}
impl ScatterData {
pub fn new(attenuation: Color, scattered: Ray) -> Self {
pub fn new(attenuation: Color, scattered: Ray, pdf: Box<dyn Pdf>) -> Self {
Self {
attenuation,
scattered,
pdf,
}
}
}
@ -19,9 +21,21 @@ impl ScatterData {
pub trait Material: Send + Sync + Clone {
fn scatter(&self, ray_in: &Ray, hit_record: &HitRecord) -> Option<ScatterData>;
fn scattering_pdf(&self, _r_in: &Ray, _rec: &HitRecord, _scattered: &Ray) -> f64 {
return 0.0;
}
fn emits(&self) -> bool {
false
}
fn emitted(&self, _u: f64, _v: f64, _p: Point3) -> Color {
Color::new(0.0, 0.0, 0.0)
}
fn hit(&self, _u: f64, _v: f64, _p: Point3) -> bool {
true
}
}
#[derive(Clone)]
@ -40,19 +54,32 @@ impl Lambertian {
}
impl Material for Lambertian {
fn scatter(&self, r_in: &Ray, rec: &HitRecord) -> Option<ScatterData> {
let scatter_direction = rec.normal + Vec3::random_unit_vector();
let scatter_direction = if scatter_direction.near_zero() {
rec.normal
} else {
scatter_direction
};
let scattered = Ray::new(rec.p, scatter_direction);
fn scatter(&self, _r_in: &Ray, rec: &HitRecord) -> Option<ScatterData> {
let uvw = Onb::build_from_w(rec.normal);
let direction = uvw.local(Vec3::random_cosine_direction(&mut thread_rng()));
let scattered = Ray::new(rec.p, direction);
let attenuation = self.texture.value(rec.u, rec.v, rec.p);
Some(ScatterData::new(attenuation, scattered))
let pdf = Box::new(CosinePdf::new(rec.normal));
Some(ScatterData::new(attenuation, scattered, pdf))
}
fn scattering_pdf(&self, _r_in: &Ray, rec: &HitRecord, scattered: &Ray) -> f64 {
let cosine = Vec3::dot(rec.normal, scattered.direction().unit());
if cosine < 0.0 {
0.0
} else {
cosine / std::f64::consts::PI
}
}
fn hit(&self, u: f64, v: f64, p: Point3) -> bool {
self.texture.hit(u, v, p)
}
}
// May be useful for some blocks(iron, diamond, etc.)
/*
#[derive(Clone)]
pub struct Metal {
albedo: Color,
@ -80,14 +107,18 @@ impl Material for Metal {
}
}
}
*/
// Using this for glass would be ideal
/*
#[derive(Clone)]
pub struct Dielectric {
ior: f64,
}
impl Dielectric {
pub fn new(ior: f64) -> Self {
pub fn new(_: Box<dyn Texture>, ior: f64) -> Self {
Self { ior }
}
@ -123,6 +154,7 @@ impl Material for Dielectric {
Some(ScatterData::new(Color::new(1.0, 1.0, 1.0), scattered))
}
}
*/
#[derive(Clone)]
pub struct DiffuseLight {
@ -145,6 +177,10 @@ impl Material for DiffuseLight {
None
}
fn emits(&self) -> bool {
true
}
fn emitted(&self, u: f64, v: f64, p: Point3) -> Color {
self.emit.value(u, v, p)
}


+ 152
- 0
src/mc_blocks.rs View File

@ -0,0 +1,152 @@
use crate::*;
// TODO wheat doesn't look great
#[derive(Clone)]
pub struct Wheat<'a> {
sides: HittableList<'a>,
aabb: Aabb,
}
impl<'a> Wheat<'a> {
pub fn new(p0: Point3, p1: Point3, mat: &'a dyn Material) -> Self {
let mut sides = HittableList::new();
let dx = p1.x() - p0.x();
let dz = p1.z() - p0.z();
let x1 = p0.x() + 0.25 * dx;
let x2 = p0.x() + 0.75 * dx;
let z1 = p0.z() + 0.25 * dz;
let z2 = p0.z() + 0.75 * dz;
sides.add(Box::new(XyRect::new(
p0.x(),
p1.x(),
p0.y(),
p1.y(),
z1,
mat,
false,
)));
sides.add(Box::new(XyRect::new(
p0.x(),
p1.x(),
p0.y(),
p1.y(),
z2,
mat,
false,
)));
sides.add(Box::new(YzRect::new(
p0.y(),
p1.y(),
p0.z(),
p1.z(),
x1,
mat,
false,
)));
sides.add(Box::new(YzRect::new(
p0.y(),
p1.y(),
p0.z(),
p1.z(),
x2,
mat,
false,
)));
Self {
sides,
aabb: Aabb::new(p0, p1),
}
}
}
impl<'a> Hittable for Wheat<'a> {
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)
}
fn bounding_box(&self) -> Aabb {
self.aabb
}
}
#[derive(Clone)]
pub struct SugarCane<'a> {
sides: HittableList<'a>,
aabb: Aabb,
}
impl<'a> SugarCane<'a> {
pub fn new(p0: Point3, p1: Point3, mat: &'a dyn Material) -> Self {
let dx = p1.x() - p0.x();
let dy = p1.y() - p0.y();
let dz = p1.z() - p0.z();
let x_half = p0.x() * 0.5 + p1.x() * 0.5;
let y_half = p0.y() * 0.5 + p1.y() * 0.5;
let z_half = p0.z() * 0.5 + p1.z() * 0.5;
let mut sides = HittableList::new();
sides.add(Box::new(Translate::new(
Box::new(RotateY::new(
Box::new(XyRect::new(
-dx / 2.0,
dx / 2.0,
-dy / 2.0,
dy / 2.0,
0.0,
mat,
false,
)),
45.0,
)),
Point3::new(x_half, y_half, z_half),
)));
sides.add(Box::new(Translate::new(
Box::new(RotateY::new(
Box::new(YzRect::new(
-dy / 2.0,
dy / 2.0,
-dz / 2.0,
dz / 2.0,
0.0,
mat,
false,
)),
45.0,
)),
Point3::new(x_half, y_half, z_half),
)));
Self {
sides,
aabb: Aabb::new(p0, p1),
}
}
}
impl<'a> Hittable for SugarCane<'a> {
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)
}
fn bounding_box(&self) -> Aabb {
self.aabb
}
}

+ 19
- 0
src/mc_world.rs View File

@ -45,4 +45,23 @@ impl McWorld {
self.chunks[&(chunk_x, chunk_z)].block(block_x as usize, y, block_z as usize)
}
pub fn for_each_block<F: FnMut(isize, isize, isize, &fastanvil::Block)>(
&self,
min: (isize, isize, isize),
max: (isize, isize, isize),
mut fun: F,
) {
for x in min.0..max.0 {
for y in min.1..max.1 {
for z in min.2..max.2 {
if let Some(block) = self.block(x, y, z) {
if block.name != "minecraft:air" {
fun(x, y, z, block);
}
}
}
}
}
}
}

+ 37
- 0
src/onb.rs View File

@ -0,0 +1,37 @@
use crate::*;
pub struct Onb {
axis: [Vec3; 3],
}
impl Onb {
pub fn build_from_w(n: Vec3) -> Self {
let w = n.unit();
let a = if w.x().abs() > 0.9 {
Vec3::new(0.0, 1.0, 0.0)
} else {
Vec3::new(1.0, 0.0, 0.0)
};
let v = Vec3::cross(w, a).unit();
let u = Vec3::cross(w, v);
let axis = [u, v, w];
Self { axis }
}
pub fn u(&self) -> Vec3 {
self.axis[0]
}
pub fn v(&self) -> Vec3 {
self.axis[1]
}
pub fn w(&self) -> Vec3 {
self.axis[2]
}
pub fn local(&self, a: Vec3) -> Vec3 {
self.u() * a.x() + self.v() * a.y() + self.w() * a.z()
}
}

+ 80
- 0
src/pdf.rs View File

@ -0,0 +1,80 @@
use crate::*;
pub trait Pdf {
fn value(&self, direction: Vec3) -> f64;
fn generate(&self, rng: &mut ThreadRng) -> Vec3;
}
pub struct CosinePdf {
uvw: Onb,
}
impl CosinePdf {
pub fn new(w: Vec3) -> Self {
Self {
uvw: Onb::build_from_w(w),
}
}
}
impl Pdf for CosinePdf {
fn value(&sel