crystal_ball 0.3.0

A path tracing library written in Rust.
Documentation
use rayon::prelude::*;

use crate::math::{Bounds3, Hit, Point3, Ray, Transformable, Vec3};
use crate::shapes::{Shape, Triangle, TriangleMesh, BVH};

/// A mesh consisting of multiple shapes stored in a [`BVH`].
#[derive(Clone, Debug)]
pub struct Mesh<S: Shape> {
    /// The mesh's bounding volume hierarchy.
    pub bvh: BVH<S>,
    /// The mesh's bounding box.
    pub bounds: Bounds3,
}

impl<S: Shape> Mesh<S> {
    /// Create a new [`BVH`] from a [`Vec`] of [`Shape`].
    pub fn new(shapes: Vec<S>) -> Self {
        let bvh = BVH::init(4, shapes);

        Mesh {
            bounds: bvh
                .shapes
                .iter()
                .fold(Bounds3::default(), |b, s| b.include_bounds(s.bounds())),
            bvh,
        }
    }

    /// Calculate the bounding box of the underlying [`BVH`].
    fn calculate_bounds(&self) -> Bounds3 {
        self.bvh
            .shapes
            .iter()
            .fold(Bounds3::default(), |b, s| b.include_bounds(s.bounds()))
    }
}

impl<S: Shape> Shape for Mesh<S> {
    fn intersects(&self, ray: Ray) -> Option<Hit> {
        self.bvh.intersects(ray).map(|(h, _)| h)
    }

    fn bounds(&self) -> Bounds3 {
        self.bounds
    }
}

impl<I: IntoIterator<Item = TriangleMesh>> From<I> for Mesh<Triangle> {
    fn from(value: I) -> Self {
        Mesh::new(value.into_iter().flat_map(|m| m.triangles()).collect())
    }
}

// TODO: make rotate and scale methods use the translation as origin
impl<S: Shape + Transformable + Clone> Transformable for Mesh<S> {
    // TODO: translate_x, translate_y, translate_z methods
    fn translate(mut self, translation: Vec3) -> Self {
        let shapes = self
            .bvh
            .shapes
            .into_par_iter()
            .map(|s| s.translate(translation))
            .collect();

        self.bvh = BVH::init(4, shapes);
        self.bounds = self.calculate_bounds();

        self
    }

    fn rotate(mut self, origin: Point3, axis: Vec3, angle: f64) -> Self {
        let shapes = self
            .bvh
            .shapes
            .into_par_iter()
            .map(|s| s.rotate(origin, axis, angle))
            .collect();

        self.bvh = BVH::init(4, shapes);
        self.bounds = self.calculate_bounds();

        self
    }

    fn rotate_x(self, angle: f64) -> Self {
        self.rotate(Point3::default(), Vec3::X, angle)
    }

    fn rotate_y(self, angle: f64) -> Self {
        self.rotate(Point3::default(), Vec3::Y, angle)
    }

    fn rotate_z(self, angle: f64) -> Self {
        self.rotate(Point3::default(), Vec3::Z, angle)
    }

    fn scale_x(self, factor: f64) -> Self {
        self.scale(Point3::default(), Vec3::new(factor, 1.0, 1.0))
    }

    fn scale_y(self, factor: f64) -> Self {
        self.scale(Point3::default(), Vec3::new(1.0, factor, 1.0))
    }

    fn scale_z(self, factor: f64) -> Self {
        self.scale(Point3::default(), Vec3::new(1.0, 1.0, factor))
    }

    fn scale_xyz(self, scale: Vec3) -> Self {
        self.scale(Point3::default(), scale)
    }

    fn scale(mut self, origin: Point3, scale: Vec3) -> Self {
        let shapes = self
            .bvh
            .shapes
            .into_par_iter()
            .map(|s| s.scale(origin, scale))
            .collect();

        self.bvh = BVH::init(4, shapes);
        self.bounds = self.calculate_bounds();

        self
    }

    // TODO: Implement when I figured out how to preserve the scale
    /// This method is currently ***not*** implemented.
    fn look_at(self, _target: Point3, _view_up: Vec3) -> Self {
        todo!()
    }
}