crystal_ball 0.3.0

A path tracing library written in Rust.
Documentation
use std::sync::Arc;

use crate::math::{Point2, Point3, Transform, Transformable, Vec3, Vec4};
use crate::prelude::Triangle;

/// A mesh consisting of multiple triangles.
///
/// When applying transformations such as [`Transformable::rotate_x`],
/// [`TriangleMesh::vertex_mean`] is used as the origin.
///
/// This struct does not store any [`Triangle`]s directly.
/// Instead it contains the necessary buffers to create the individual triangles.
#[derive(Clone)]
pub struct TriangleMesh {
    pub indices: Vec<u32>,
    pub vertices: Vec<Point3>,
    pub normals: Vec<Vec3>,
    pub tangents: Vec<Vec4>,
    pub uvs: Vec<Point2>,
}

impl TriangleMesh {
    /// Create a new [`TriangleMesh`].
    pub fn new(
        indices: Vec<u32>,
        vertices: Vec<Point3>,
        normals: Vec<Vec3>,
        tangents: Vec<Vec4>,
        uvs: Vec<Point2>,
    ) -> Self {
        Self {
            indices,
            vertices,
            normals,
            tangents,
            uvs,
        }
    }

    /// Create a new [`TriangleMesh`] consisting of a single triangle.
    pub fn triangle(leg_length: f64) -> TriangleMesh {
        let indices = vec![0, 1, 2];
        let vertices = vec![
            Point3::new(leg_length, 0.0, -leg_length),
            Point3::new(-leg_length, 0.0, leg_length),
            Point3::new(leg_length, 0.0, leg_length),
        ];
        let normals = vec![Vec3::Y; 3];
        let tangents = vec![];
        let uvs = vec![];

        TriangleMesh::new(indices, vertices, normals, tangents, uvs)
    }

    /// Create a new [`TriangleMesh`] consisting of a single plane.
    pub fn plane(half_size: f64) -> TriangleMesh {
        let indices = vec![0, 1, 2, 0, 3, 1];
        let vertices = vec![
            Point3::new(half_size, 0.0, -half_size),
            Point3::new(-half_size, 0.0, half_size),
            Point3::new(half_size, 0.0, half_size),
            Point3::new(-half_size, 0.0, -half_size),
        ];
        let normals = vec![Vec3::Y; 4];
        let tangents = vec![];
        let uvs = vec![];

        TriangleMesh::new(indices, vertices, normals, tangents, uvs)
    }

    /// Create a new [`TriangleMesh`] consisting of a single cube.
    pub fn cube(half_size: f64) -> TriangleMesh {
        let indices = vec![
            0, 1, 2, 0, 3, 1, 4, 5, 6, 4, 7, 5, 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15, 16,
            17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23,
        ];
        let vertices = vec![
            Point3::new(half_size, half_size, -half_size),
            Point3::new(-half_size, half_size, half_size),
            Point3::new(half_size, half_size, half_size),
            Point3::new(-half_size, half_size, -half_size),
            Point3::new(half_size, -half_size, -half_size),
            Point3::new(-half_size, -half_size, half_size),
            Point3::new(half_size, -half_size, half_size),
            Point3::new(-half_size, -half_size, -half_size),
            Point3::new(half_size, -half_size, half_size),
            Point3::new(half_size, half_size, half_size),
            Point3::new(half_size, half_size, -half_size),
            Point3::new(half_size, -half_size, -half_size),
            Point3::new(-half_size, -half_size, half_size),
            Point3::new(-half_size, half_size, half_size),
            Point3::new(-half_size, half_size, -half_size),
            Point3::new(-half_size, -half_size, -half_size),
            Point3::new(half_size, -half_size, half_size),
            Point3::new(-half_size, -half_size, half_size),
            Point3::new(-half_size, half_size, half_size),
            Point3::new(half_size, half_size, half_size),
            Point3::new(half_size, -half_size, -half_size),
            Point3::new(-half_size, -half_size, -half_size),
            Point3::new(-half_size, half_size, -half_size),
            Point3::new(half_size, half_size, -half_size),
        ];
        let normals = vec![
            Vec3::Y,
            Vec3::Y,
            Vec3::Y,
            Vec3::Y,
            -Vec3::Y,
            -Vec3::Y,
            -Vec3::Y,
            -Vec3::Y,
            Vec3::X,
            Vec3::X,
            Vec3::X,
            Vec3::X,
            -Vec3::X,
            -Vec3::X,
            -Vec3::X,
            -Vec3::X,
            Vec3::Z,
            Vec3::Z,
            Vec3::Z,
            Vec3::Z,
            -Vec3::Z,
            -Vec3::Z,
            -Vec3::Z,
            -Vec3::Z,
        ];
        let tangents = vec![];
        let uvs = vec![];

        TriangleMesh::new(indices, vertices, normals, tangents, uvs)
    }

    /// Calculate the triangle mesh's vertex mean.
    pub fn vertex_mean(&self) -> Point3 {
        self.vertices.iter().fold(Point3::ZERO, |acc, p| acc + *p) / self.vertices.len() as f64
    }

    /// Return an iterator over the triangle mesh's individual triangles.
    ///
    /// These triangles can then be used to construct a [`Mesh`](crate::shapes::Mesh).
    pub fn triangles(self) -> impl Iterator<Item = Triangle> {
        let triangle_count = self.indices.len() / 3;
        let triangle_mesh = Arc::new(self);

        (0..triangle_count).map(move |i| Triangle::new(Arc::clone(&triangle_mesh), i))
    }
}

impl Transformable for TriangleMesh {
    fn translate(mut self, translation: Vec3) -> Self {
        for vertex in self.vertices.iter_mut() {
            *vertex += translation;
        }

        self
    }

    fn rotate(mut self, origin: Point3, axis: Vec3, angle: f64) -> Self {
        let transform = Transform::rotate(origin, axis, angle);

        for vertex in self.vertices.iter_mut() {
            *vertex = transform.mat4 * *vertex;
        }

        for normal in self.normals.iter_mut() {
            *normal = transform.mat4 * *normal;
        }

        self
    }

    fn rotate_x(mut self, angle: f64) -> Self {
        let vertex_mean = self.vertex_mean();
        let transform = Transform::rotate_x(vertex_mean, angle);

        for vertex in self.vertices.iter_mut() {
            *vertex = transform.mat4 * *vertex;
        }

        for normal in self.normals.iter_mut() {
            *normal = transform.mat4 * *normal;
        }

        self
    }

    fn rotate_y(mut self, angle: f64) -> Self {
        let vertex_mean = self.vertex_mean();
        let transform = Transform::rotate_y(vertex_mean, angle);

        for vertex in self.vertices.iter_mut() {
            *vertex = vertex_mean + transform.mat4 * (*vertex - vertex_mean);
        }

        for normal in self.normals.iter_mut() {
            *normal = transform.mat4 * *normal;
        }

        self
    }

    fn rotate_z(mut self, angle: f64) -> Self {
        let vertex_mean = self.vertex_mean();
        let transform = Transform::rotate_z(vertex_mean, angle);

        for vertex in self.vertices.iter_mut() {
            *vertex = vertex_mean + transform.mat4 * (*vertex - vertex_mean);
        }

        for normal in self.normals.iter_mut() {
            *normal = transform.mat4 * *normal;
        }

        self
    }

    fn scale_x(self, factor: f64) -> Self {
        let vertex_mean = self.vertex_mean();

        self.scale(vertex_mean, Vec3::new(factor, 1.0, 1.0))
    }

    fn scale_y(self, factor: f64) -> Self {
        let vertex_mean = self.vertex_mean();

        self.scale(vertex_mean, Vec3::new(1.0, factor, 1.0))
    }

    fn scale_z(self, factor: f64) -> Self {
        let vertex_mean = self.vertex_mean();

        self.scale(vertex_mean, Vec3::new(1.0, 1.0, factor))
    }

    fn scale_xyz(self, scale: Vec3) -> Self {
        let vertex_mean = self.vertex_mean();

        self.scale(vertex_mean, scale)
    }

    fn scale(mut self, origin: Point3, scale: Vec3) -> Self {
        let transform = Transform::scale(origin, scale);

        for vertex in self.vertices.iter_mut() {
            *vertex = transform.mat4 * *vertex;
        }

        for normal in self.normals.iter_mut() {
            *normal = transform.mat4 * *normal;
        }

        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!()
    }
}