Skip to content

Commit

Permalink
Chap 16: CSG
Browse files Browse the repository at this point in the history
  • Loading branch information
fremag committed Mar 15, 2024
1 parent 6e1b542 commit 1b9b55f
Show file tree
Hide file tree
Showing 12 changed files with 225 additions and 11 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
workspace = { members = [ "ray-tracer-lib", "ray-tracer-cli"], resolver = "2" }

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html


1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ I'm learning Rust while working my way through Jamis Buck's

<details open>
<summary>Chapter 16: Constructive Solid Geometry</summary>
![menger](https://raw.githubusercontent.com/fremag/ray_tracer_rust/main/img/menger_sponge_scene.png)

![csg](https://raw.githubusercontent.com/fremag/ray_tracer_rust/main/img/csg_scene.png)
</details>
Expand Down
Binary file added img/menger_castle_scene.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added img/menger_sponge_scene.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 4 additions & 1 deletion ray-tracer-cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::scenes::cube_scene::CubeScene;
use crate::scenes::cylinder_scene::CylinderScene;
use crate::scenes::dragon_scene::DragonScene;
use crate::scenes::group_scene::GroupScene;
use crate::scenes::menger_scene::{MengerCastleScene, MengerSpongeScene};
use crate::scenes::patterns_scene::PatternsScene;
use crate::scenes::refraction_sphere_scene::RefractionSphereScene;
use crate::scenes::smooth_teapot_scene::SmoothTeaPotScene;
Expand All @@ -30,7 +31,7 @@ fn main() {
}

println!("Start...");
render(&CsgScene{},400, 400, "./img/csg_scene.png");
render(&MengerCastleScene{},800, 800, "./img/menger_castle_scene.png");
println!("Done.")
}

Expand All @@ -48,6 +49,8 @@ fn render_all() {
render(&TeaPotScene{},400, 400, "./img/teapot.png");
render(&DragonScene { }, 800, 600, "./img/dragon.png");
render(&SmoothTeaPotScene{},400, 400, "./img/teapot_smooth.png");
render(&CsgScene{},400, 400, "./img/csg_scene.png");
render(&MengerSpongeScene{},400, 400, "./img/menger_sponge_scene.png");
}

fn render(scene : &dyn Scene, h_size : usize, v_size : usize, file_path: &str) {
Expand Down
155 changes: 155 additions & 0 deletions ray-tracer-cli/src/scenes/menger_scene.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
use ray_tracer_lib::camera::Camera;
use ray_tracer_lib::colors::Color;
use ray_tracer_lib::core::math::{Float, PI};
use ray_tracer_lib::core::transform::{rotation_x, rotation_y, scaling, translation, view_transform};
use ray_tracer_lib::core::tuple::{point, vector};
use ray_tracer_lib::lights::point_light::PointLight;
use ray_tracer_lib::material::Material;
use ray_tracer_lib::object::{build_cube, build_plane, Object};
use ray_tracer_lib::patterns::pattern::Pattern;
use ray_tracer_lib::shapes::csg::{Csg, CsgOperation};
use ray_tracer_lib::world::World;
use crate::scene::Scene;

pub struct MengerSpongeScene {}

impl Scene for MengerSpongeScene {
fn get_world(&self) -> World {
let mut world = World::new();
let lights = vec!(
PointLight::new(point(-1.0, 5.0, -5.0), Color::new(1.0/2.0, 1.0/2.0, 1.0/2.0)),
PointLight::new(point(-5.0, 5.0, 1.0), Color::new(1.0/4.0, 1.0/4.0, 1.0/4.0, )),
PointLight::new(point(5.0, 5.0, -1.0), Color::new(1.0/6.0, 1.0/6.0, 1.0/6.0 )),
);
world.set_lights(lights);

let mut material_floor = Material::new();
material_floor.pattern = Pattern::checker(Color::white(), Color::black());

let mut floor = build_plane();
floor.set_material(material_floor.clone());
world.objects.push(floor);

let mut menger_sponge = MengerSponge::new(2);
menger_sponge.set_transformation(translation(0.0, 1.5, 0.0));

world.objects.push(menger_sponge);

world
}

fn get_camera(&self, h_size: usize, v_size: usize) -> Camera {
let mut camera = Camera::new(h_size, v_size, PI / 3.0);
camera.set_transform(view_transform(point(-1.0, 4.0, -6.0),
point(0.0, 2.5, 0.0),
vector(0.0, 2.0, 0.0)));
camera
}
}
pub struct MengerCastleScene {}

impl Scene for MengerCastleScene {
fn get_world(&self) -> World {
let mut world = World::new();
let lights = vec!(
PointLight::new(point(-1.0, 6.0, -5.0), Color::new(1.0/2.0, 1.0/2.0, 1.0/2.0)),
PointLight::new(point(-5.0, 6.0, -1.0), Color::new(1.0/4.0, 1.0/4.0, 1.0/4.0, )),
PointLight::new(point(5.0, 6.0, -1.0), Color::new(1.0/6.0, 1.0/6.0, 1.0/6.0 )),
);
world.set_lights(lights);

let mut material_floor = Material::new();
material_floor.pattern = Pattern::checker(Color::white(), Color::black());

let mut floor = build_plane();
floor.set_material(material_floor.clone());
world.objects.push(floor);

let menger_sponge = MengerSponge::new(3);
let mut cube = build_cube();
cube.set_transformation(&scaling(2.0, 1.0, 2.0) * &translation(0.0, 1.0, 0.0));

let mut menger_castle = Object::new_csg(Csg::new(CsgOperation::Difference, menger_sponge, cube));
menger_castle.set_transformation(translation(0.0, 1.5, 0.00));
world.objects.push(menger_castle);

world
}

fn get_camera(&self, h_size: usize, v_size: usize) -> Camera {
let mut camera = Camera::new(h_size, v_size, PI / 3.0);
camera.set_transform(view_transform(point(2.0, 3.5, -2.0),
point(0.0, 0.0, 0.0),
vector(0.0, 1.0, 0.0)));
camera
}
}

pub struct MengerSponge
{}

impl MengerSponge {
pub fn new(m: usize) -> Object {
let thing1 = Self::make_cubes(-1.5, -1.5, 1.5, 1.5, 0, m);
let mut thing2 = Self::make_cubes(-1.5, -1.5, 1.5, 1.5, 0, m);
let mut thing3 = Self::make_cubes(-1.5, -1.5, 1.5, 1.5, 0, m);

thing2.set_transformation(rotation_x(PI / 2.0));
thing3.set_transformation(rotation_y(PI / 2.0));

let u1 = Object::new_csg(Csg::new(CsgOperation::Union, thing3, thing1));
let u2 = Object::new_csg(Csg::new(CsgOperation::Union, u1, thing2));

//
let mut cube = build_cube();
cube.set_transformation(scaling(1.5, 1.5, 1.5));
//
let sponge = Object::new_csg(Csg::new(CsgOperation::Difference, cube, u2));
sponge
}

fn make_cubes(x1: Float, y1: Float, x2: Float, y2: Float, n: usize, max: usize) -> Object
{
let delta_x = (x2 - x1) / 3.0;
let delta_y = (x2 - x1) / 3.0;

let sx = delta_x / 2.0;
let sy = delta_y / 2.0;
let mut cube = build_cube();
let tx = x1 + (x2 - x1) / 2.0;
let ty = y1 + (y2 - y1) / 2.0;
cube.set_transformation(&translation(tx, ty, 0.0) * &scaling(sx, sy, 2.1));

if n >= max {
return cube;
}

// left col
let g1 = Self::make_cubes(x1 + 0.0 * delta_x, y1 + 0.0 * delta_y, x1 + 1.0 * delta_x, y1 + 1.0 * delta_y, n + 1, max);
let g2 = Self::make_cubes(x1 + 0.0 * delta_x, y1 + 1.0 * delta_y, x1 + 1.0 * delta_x, y1 + 2.0 * delta_y, n + 1, max);
let g3 = Self::make_cubes(x1 + 0.0 * delta_x, y1 + 2.0 * delta_y, x1 + 1.0 * delta_x, y1 + 3.0 * delta_y, n + 1, max);

// center col
let g4 = Self::make_cubes(x1 + 1.0 * delta_x, y1 + 0.0 * delta_y, x1 + 2.0 * delta_x, y1 + 1.0 * delta_y, n + 1, max);
//var g5 = MakeCubes(x1 + 1*deltaX, y1+1*deltaY, x1 + 2*deltaX, y1 + 2*deltaY, n + 1, max);
let g6 = Self::make_cubes(x1 + 1.0 * delta_x, y1 + 2.0 * delta_y, x1 + 2.0 * delta_x, y1 + 3.0 * delta_y, n + 1, max);

// right col
let g7 = Self::make_cubes(x1 + 2.0 * delta_x, y1 + 0.0 * delta_y, x1 + 3.0 * delta_x, y1 + 1.0 * delta_y, n + 1, max);
let g8 = Self::make_cubes(x1 + 2.0 * delta_x, y1 + 1.0 * delta_y, x1 + 3.0 * delta_x, y1 + 2.0 * delta_y, n + 1, max);
let g9 = Self::make_cubes(x1 + 2.0 * delta_x, y1 + 2.0 * delta_y, x1 + 3.0 * delta_x, y1 + 3.0 * delta_y, n + 1, max);

let u1 = Object::new_csg(Csg::new(CsgOperation::Union, g1, g2));
let u2 = Object::new_csg(Csg::new(CsgOperation::Union, u1, g3));
let u3 = Object::new_csg(Csg::new(CsgOperation::Union, g4, g6));

let u4 = Object::new_csg(Csg::new(CsgOperation::Union, g7, g8));
let u5 = Object::new_csg(Csg::new(CsgOperation::Union, g9, u4));

let u6 = Object::new_csg(Csg::new(CsgOperation::Union, u2, u3));
let u7 = Object::new_csg(Csg::new(CsgOperation::Union, u5, u6));

let u8 = Object::new_csg(Csg::new(CsgOperation::Union, cube, u7));
u8
}
}
1 change: 1 addition & 0 deletions ray-tracer-cli/src/scenes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ pub mod teapot_scene;
pub mod dragon_scene;
pub mod smooth_teapot_scene;
pub mod csg_scene;
pub mod menger_scene;
24 changes: 18 additions & 6 deletions ray-tracer-lib/src/object.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,13 +75,15 @@ impl Object {
INTERSECTION_COUNTER.fetch_add(1, Ordering::SeqCst);
}

let transformed_ray = ray.transform(&self.transformation_inverse);
return match &self.object_type {
ObjectShape(shape) => intersections(shape.intersect(&transformed_ray).iter().map(|t| Intersection::new(*t, self.clone() )).collect()),
ObjectGroup(group) => group.intersect(&transformed_ray),
ObjectShape(shape) => {
let transformed_ray = ray.transform(&self.transformation_inverse);
intersections(shape.intersect(&transformed_ray).iter().map(|t| Intersection::new(*t, self.clone() )).collect())
},
ObjectGroup(group) => group.intersect(&ray),
TriangleGroup(model) => {

let v = model.intersect(&transformed_ray).into_iter().map(|(t, triangle)| {
let v = model.intersect(&ray).into_iter().map(|(t, triangle)| {
let id = triangle.id;
let mut obj = Object::new_with_id(Shape::Triangle(triangle), id);
obj.set_material(self.material);
Expand All @@ -90,15 +92,15 @@ impl Object {
intersections(v)
},
SmoothTriangleGroup(model) => {
let v = model.intersect(&transformed_ray).into_iter().map(|(t, smooth_triangle, u, v)| {
let v = model.intersect(&ray).into_iter().map(|(t, smooth_triangle, u, v)| {
let id = smooth_triangle.triangle.id;
let mut obj = Object::new_with_id(Shape::SmoothTriangle(smooth_triangle), id);
obj.set_material(self.material);
Intersection::new_uv(t, obj, u, v)
}).collect();
intersections(v)
},
CsgGroup(csg) => csg.intersect(&transformed_ray)
CsgGroup(csg) => csg.intersect(&ray)
};
}

Expand Down Expand Up @@ -223,6 +225,16 @@ impl Object {
transformation_inverse_transpose: Matrix::<4>::identity(),
}
}

pub fn get_child_ids(&self) -> Vec<usize>{
match &self.object_type {
ObjectShape(_) => vec![],
ObjectGroup(group) => group.get_child_ids(),
TriangleGroup(triangle_group) => triangle_group.get_child_ids(),
SmoothTriangleGroup(smooth_triangle_group) => smooth_triangle_group.get_child_ids(),
CsgGroup(csg) => csg.get_child_ids()
}
}
}

impl PartialEq for Object {
Expand Down
22 changes: 19 additions & 3 deletions ray-tracer-lib/src/shapes/csg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,15 @@ pub struct Csg {

impl Csg {
pub(crate) fn intersect(&self, ray: &Ray) -> Intersections {
let mut left_xs = self.left().intersect(ray);
let right_xs = self.right().intersect(ray);
let intersections_with_bounds = self.group.bounds().intersect(&ray);
if intersections_with_bounds.is_empty() {
return intersections(vec![]);
}

let left = self.left();
let mut left_xs = left.intersect(ray);
let right = self.right();
let right_xs = right.intersect(ray);

for intersection in right_xs.intersections.iter() {
left_xs.push(intersection.clone());
Expand Down Expand Up @@ -71,7 +78,8 @@ impl Csg {
for i in xs.intersections.iter() {
// if i.object is part of the "left" child, then lhit is true
let obj = &i.object;
let lhit = self.left().includes(obj);
let left = self.left();
let lhit = left.includes(obj);
if Csg::intersection_allowed(&self.csg_operation, lhit, inl, inr) {
let intersection = Intersection::new(i.t, i.object.clone());
result.intersections.push(intersection);
Expand All @@ -86,4 +94,12 @@ impl Csg {

result
}

pub(crate) fn get_child_ids(&self) -> Vec<usize> {
let mut ids : Vec<usize> = self.left().get_child_ids().into_iter().collect();
let ids_right = self.right().get_child_ids();
ids.extend(ids_right);

ids
}
}
14 changes: 13 additions & 1 deletion ray-tracer-lib/src/shapes/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,18 @@ impl Group {
}
self.children.push(transformed_children);
self.children_ids.insert(child.object_id);
for id in child.get_child_ids() {
self.children_ids.insert(id);
}
}
pub fn get_child_ids(&self) -> Vec<usize> {
let mut ids = vec![];
for child in &self.children {
for id in child.get_child_ids() {
ids.push(id);
}
}
ids
}

pub fn intersect(&self, ray: &Ray) -> Intersections {
Expand All @@ -89,7 +101,7 @@ impl Group {
return true;
}
let groups = self.children.iter().filter(|child| match &child.object_type {
ObjectType::ObjectShape(_) => true, _ => false
ObjectType::ObjectShape(_) => false, _ => true
} );
for group in groups{
if group.includes(object) {
Expand Down
6 changes: 6 additions & 0 deletions ray-tracer-lib/src/shapes/smooth_triangle_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,10 @@ impl SmoothTriangleModel {
pub(crate) fn includes(&self, object: &Object) -> bool {
self.triangles_ids.contains(&object.object_id)
}

pub fn get_child_ids(&self) -> Vec<usize> {
let mut ids = vec![];
ids.extend(self.triangles_ids.clone());
ids
}
}
6 changes: 6 additions & 0 deletions ray-tracer-lib/src/shapes/triangle_model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,10 @@ impl TriangleModel {
pub(crate) fn includes(&self, object: &Object) -> bool {
self.triangles_ids.contains(&object.object_id)
}

pub fn get_child_ids(&self) -> Vec<usize> {
let mut ids = vec![];
ids.extend(self.triangles_ids.clone());
ids
}
}

0 comments on commit 1b9b55f

Please sign in to comment.