From a27899fb6c246a3936cec0b66c00bb92671f36bf Mon Sep 17 00:00:00 2001 From: "Sean P. Kelly" Date: Tue, 26 Nov 2024 15:57:47 -0800 Subject: [PATCH] refactor: remove unneeded generics, use more builders This change removes the need for generic path metadata from shapes. raydeon's Materials allow associating an arbitrary `usize`, which can be used to accomplish the same thing via a lookup table after rendering. Removing the trait bounds makes it easier to maintain the library. This change also introduces the builder pattern to most of raydeon's shape types. --- README.md | 36 +++++--- pyraydeon/src/scene.rs | 37 ++++---- pyraydeon/src/shapes/mod.rs | 25 ++--- pyraydeon/src/shapes/primitive.rs | 106 +++++++++++---------- raydeon/Cargo.toml | 2 +- raydeon/examples/cube.rs | 10 +- raydeon/examples/geom_perf.rs | 16 ++-- raydeon/examples/lit_cubes.rs | 36 +++++--- raydeon/examples/triangles.rs | 24 +++-- raydeon/src/bvh.rs | 148 +++++++++--------------------- raydeon/src/camera.rs | 6 +- raydeon/src/lib.rs | 22 ++--- raydeon/src/lights.rs | 16 ++-- raydeon/src/material.rs | 26 ++++++ raydeon/src/path.rs | 96 +++++++++---------- raydeon/src/scene.rs | 57 ++++++------ raydeon/src/shapes/aacuboid.rs | 79 ++++++++-------- raydeon/src/shapes/plane.rs | 21 +++-- raydeon/src/shapes/quad.rs | 75 +++++++-------- raydeon/src/shapes/sphere.rs | 27 ++---- raydeon/src/shapes/triangle.rs | 76 ++++++++------- 21 files changed, 448 insertions(+), 493 deletions(-) diff --git a/README.md b/README.md index 91dd5b1..d91c219 100644 --- a/README.md +++ b/README.md @@ -35,21 +35,27 @@ fn main() { let scene = Scene::new() .geometry(vec![ - Arc::new(AxisAlignedCuboid::tagged( - (-1.0, -1.0, -1.0), - (1.0, 1.0, 1.0), - Material::new(3.0, 2.0, 2.0, 0), - )), - Arc::new(AxisAlignedCuboid::tagged( - (1.8, -1.0, -1.0), - (3.8, 1.0, 1.0), - Material::new(2.0, 2.0, 2.0, 0), - )), - Arc::new(AxisAlignedCuboid::tagged( - (-1.4, 1.8, -1.0), - (0.6, 3.8, 1.0), - Material::new(3.0, 2.0, 2.0, 0), - )), + Arc::new( + AxisAlignedCuboid::new() + .min((-1.0, -1.0, -1.0)) + .max((1.0, 1.0, 1.0)) + .material(Material::new(3.0, 2.0, 2.0, 0)) + .build(), + ), + Arc::new( + AxisAlignedCuboid::new() + .min((1.8, -1.0, -1.0)) + .max((3.8, 1.0, 1.0)) + .material(Material::new(2.0, 2.0, 2.0, 0)) + .build(), + ), + Arc::new( + AxisAlignedCuboid::new() + .min((-1.4, 1.8, -1.0)) + .max((0.6, 3.8, 1.0)) + .material(Material::new(3.0, 2.0, 2.0, 0)) + .build(), + ), ]) .lighting( SceneLighting::new() diff --git a/pyraydeon/src/scene.rs b/pyraydeon/src/scene.rs index 26e07cf..1f0c85f 100644 --- a/pyraydeon/src/scene.rs +++ b/pyraydeon/src/scene.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use numpy::{Ix1, PyArray, PyReadonlyArray1}; use pyo3::prelude::*; -use raydeon::{SceneLighting, WorldSpace}; +use raydeon::SceneLighting; use crate::light::PointLight; use crate::linear::{ArbitrarySpace, Point2, Point3, Vec3}; @@ -108,7 +108,7 @@ impl Camera { #[pyclass(frozen)] pub(crate) struct Scene { - scene: Arc>, + scene: Arc, } #[pymethods] @@ -123,16 +123,15 @@ impl Scene { ) -> PyResult { let geometry = geometry.unwrap_or_default(); let lights = lights.unwrap_or_default(); - let geometry: Vec>> = - geometry - .into_iter() - .map(|g| { - let geom: Py = g.extract(py)?; - let raydeon_shape = geom.borrow(py); - let raydeon_shape = raydeon_shape.geometry(g); - Ok(raydeon_shape) - }) - .collect::>()?; + let geometry: Vec> = geometry + .into_iter() + .map(|g| { + let geom: Py = g.extract(py)?; + let raydeon_shape = geom.borrow(py); + let raydeon_shape = raydeon_shape.geometry(g); + Ok(raydeon_shape) + }) + .collect::>()?; let lights: Vec> = lights .into_iter() .map(|l| Arc::new(l.0) as Arc) @@ -216,7 +215,7 @@ impl LineSegment2D { } } -pywrap!(LineSegment3D, raydeon::path::LineSegment3D); +pywrap!(LineSegment3D, raydeon::path::LineSegment3D); #[pymethods] impl LineSegment3D { @@ -229,12 +228,14 @@ impl LineSegment3D { ) -> PyResult { let p1 = Point3::try_from(p1)?; let p2 = Point3::try_from(p2)?; - Ok(raydeon::path::LineSegment3D::tagged( - p1.cast_unit(), - p2.cast_unit(), - material.unwrap_or_default().0, + Ok( + raydeon::path::LineSegment3D::new( + p1.cast_unit(), + p2.cast_unit(), + material.map(|i| i.0), + ) + .into(), ) - .into()) } #[getter] diff --git a/pyraydeon/src/shapes/mod.rs b/pyraydeon/src/shapes/mod.rs index 70a7cbd..43e345a 100644 --- a/pyraydeon/src/shapes/mod.rs +++ b/pyraydeon/src/shapes/mod.rs @@ -12,11 +12,9 @@ use crate::material::Material; use crate::ray::{HitData, Ray, AABB3}; use crate::scene::{Camera, LineSegment3D}; -type RMaterial = raydeon::material::Material; - #[derive(Debug)] enum InnerGeometry { - Native(Arc>), + Native(Arc), Py, } @@ -27,7 +25,7 @@ pub(crate) struct Geometry { } impl Geometry { - pub(crate) fn native(geom: Arc>) -> Self { + pub(crate) fn native(geom: Arc) -> Self { let geom = InnerGeometry::Native(geom); Self { geom } } @@ -37,7 +35,7 @@ impl Geometry { Self { geom } } - pub(crate) fn geometry(&self, obj: PyObject) -> Arc> { + pub(crate) fn geometry(&self, obj: PyObject) -> Arc { match &self.geom { InnerGeometry::Native(ref geom) => Arc::clone(geom), InnerGeometry::Py => Arc::new(PythonGeometry::new(obj, PythonGeometryKind::Draw)), @@ -90,7 +88,7 @@ impl Geometry { #[derive(Debug)] enum InnerCollisionGeometry { - Native(Arc>), + Native(Arc), Py, } @@ -101,7 +99,7 @@ pub(crate) struct CollisionGeometry { } impl CollisionGeometry { - pub(crate) fn native(geom: Arc>) -> Self { + pub(crate) fn native(geom: Arc) -> Self { let geom = InnerCollisionGeometry::Native(geom); Self { geom } } @@ -176,8 +174,8 @@ impl PythonGeometry { } } -impl raydeon::Shape for PythonGeometry { - fn collision_geometry(&self) -> Option>>> { +impl raydeon::Shape for PythonGeometry { + fn collision_geometry(&self) -> Option>> { let collision_geometry: Option<_> = Python::with_gil(|py| { let inner = self.slf.bind(py); let call_result = inner.call_method1("collision_geometry", ()).ok()?; @@ -189,7 +187,7 @@ impl raydeon::Shape for PythonGeometry .map(|obj| { Ok( Arc::new(PythonGeometry::as_collision_geometry(obj?.into_py(py))) - as Arc>, + as Arc, ) }) .collect::>() @@ -200,10 +198,7 @@ impl raydeon::Shape for PythonGeometry collision_geometry } - fn paths( - &self, - cam: &raydeon::Camera, - ) -> Vec> { + fn paths(&self, cam: &raydeon::Camera) -> Vec> { let segments: Option<_> = Python::with_gil(|py| { let inner = self.slf.bind(py); let cam = Camera::from(cam.clone()); @@ -236,7 +231,7 @@ impl raydeon::Shape for PythonGeometry } } -impl raydeon::CollisionGeometry for PythonGeometry { +impl raydeon::CollisionGeometry for PythonGeometry { fn hit_by(&self, ray: &raydeon::Ray) -> Option { if let PythonGeometryKind::Collision { aabb: Some(aabb) } = &self.kind { raydeon::shapes::AxisAlignedCuboid::from(aabb.0.cast_unit()).hit_by(ray)?; diff --git a/pyraydeon/src/shapes/primitive.rs b/pyraydeon/src/shapes/primitive.rs index b064b25..6b0bca4 100644 --- a/pyraydeon/src/shapes/primitive.rs +++ b/pyraydeon/src/shapes/primitive.rs @@ -4,24 +4,21 @@ use crate::material::Material; use numpy::{Ix1, PyArray, PyArrayLike1, PyArrayLike2}; use pyo3::exceptions::PyIndexError; use pyo3::prelude::*; -use raydeon::WorldSpace; use std::sync::Arc; -type RMaterial = raydeon::material::Material; - #[pyclass(frozen, extends=Geometry, subclass)] -pub(crate) struct AxisAlignedCuboid(pub(crate) Arc>); +pub(crate) struct AxisAlignedCuboid(pub(crate) Arc); impl ::std::ops::Deref for AxisAlignedCuboid { - type Target = Arc>; + type Target = Arc; fn deref(&self) -> &Self::Target { &self.0 } } -impl From>> for AxisAlignedCuboid { - fn from(value: Arc>) -> Self { +impl From> for AxisAlignedCuboid { + fn from(value: Arc) -> Self { Self(value) } } @@ -38,32 +35,32 @@ impl AxisAlignedCuboid { let min: Vec3 = min.try_into()?; let max: Vec3 = max.try_into()?; - let shape = Arc::new(raydeon::shapes::AxisAlignedCuboid::tagged( - min.cast_unit(), - max.cast_unit(), - material.unwrap_or_default().0, - )); - let geom = - Geometry::native(Arc::clone(&shape) - as Arc>); + let shape = Arc::new( + raydeon::shapes::AxisAlignedCuboid::new() + .min(min.cast_unit()) + .max(max.cast_unit()) + .material(material.map(|m| m.0).unwrap_or_default()) + .build(), + ); + let geom = Geometry::native(Arc::clone(&shape) as Arc); Ok((Self(shape), geom)) } } #[pyclass(frozen, extends=Geometry, subclass)] -pub(crate) struct Tri(pub(crate) Arc>); +pub(crate) struct Tri(pub(crate) Arc); impl ::std::ops::Deref for Tri { - type Target = Arc>; + type Target = Arc; fn deref(&self) -> &Self::Target { &self.0 } } -impl From>> for Tri { - fn from(value: Arc>) -> Self { +impl From> for Tri { + fn from(value: Arc) -> Self { Self(value) } } @@ -82,14 +79,15 @@ impl Tri { let p2: Point3 = p2.try_into()?; let p3: Point3 = p3.try_into()?; - let shape = Arc::new(raydeon::shapes::Triangle::tagged( - p1.cast_unit(), - p2.cast_unit(), - p3.cast_unit(), - material.unwrap_or_default().0, - )); - let geom = - Geometry::native(Arc::clone(&shape) as Arc>); + let shape = Arc::new( + raydeon::shapes::Triangle::new() + .v0(p1.cast_unit()) + .v1(p2.cast_unit()) + .v2(p3.cast_unit()) + .material(material.map(|m| m.0).unwrap_or_default()) + .build(), + ); + let geom = Geometry::native(Arc::clone(&shape) as Arc); Ok((Self(shape), geom)) } } @@ -121,13 +119,14 @@ impl Plane { let point: Point3 = point.try_into()?; let normal: Vec3 = normal.try_into()?; - let shape = Arc::new(raydeon::shapes::Plane::new( - point.0.cast_unit(), - normal.0.cast_unit(), - )); - let geom = CollisionGeometry::native( - Arc::clone(&shape) as Arc> + let shape = Arc::new( + raydeon::shapes::Plane::new() + .point(point.0.cast_unit()) + .normal(normal.0.cast_unit()) + .build(), ); + let geom = + CollisionGeometry::native(Arc::clone(&shape) as Arc); Ok((Self(shape), geom)) } @@ -162,13 +161,17 @@ impl From> for Sphere { #[pymethods] impl Sphere { #[new] - fn new(point: &Bound<'_, PyAny>, radius: f64) -> PyResult<(Self, CollisionGeometry)> { - let point: Point3 = point.try_into()?; - - let shape = Arc::new(raydeon::shapes::Sphere::new(point.0.cast_unit(), radius)); - let geom = CollisionGeometry::native( - Arc::clone(&shape) as Arc> + fn new(center: &Bound<'_, PyAny>, radius: f64) -> PyResult<(Self, CollisionGeometry)> { + let center: Point3 = center.try_into()?; + + let shape = Arc::new( + raydeon::shapes::Sphere::new() + .center(center.0.cast_unit()) + .radius(radius) + .build(), ); + let geom = + CollisionGeometry::native(Arc::clone(&shape) as Arc); Ok((Self(shape), geom)) } @@ -184,18 +187,18 @@ impl Sphere { } #[pyclass(frozen, extends=Geometry, subclass)] -pub(crate) struct Quad(pub(crate) Arc>); +pub(crate) struct Quad(pub(crate) Arc); impl ::std::ops::Deref for Quad { - type Target = Arc>; + type Target = Arc; fn deref(&self) -> &Self::Target { &self.0 } } -impl From>> for Quad { - fn from(value: Arc>) -> Self { +impl From> for Quad { + fn from(value: Arc) -> Self { Self(value) } } @@ -241,14 +244,15 @@ impl Quad { }) .map(|dims| [dims[0], dims[1]])?; - let shape = Arc::new(raydeon::shapes::Quad::tagged( - origin.0.cast_unit(), - basis, - dims, - material.unwrap_or_default().0, - )); - let geom = - Geometry::native(Arc::clone(&shape) as Arc>); + let shape = Arc::new( + raydeon::shapes::Quad::new() + .origin(origin.0.cast_unit()) + .basis(basis) + .dims(dims) + .material(material.map(|m| m.0).unwrap_or_default()) + .build(), + ); + let geom = Geometry::native(Arc::clone(&shape) as Arc); Ok((Self(shape), geom)) } } diff --git a/raydeon/Cargo.toml b/raydeon/Cargo.toml index 7632d77..a1f3bf5 100644 --- a/raydeon/Cargo.toml +++ b/raydeon/Cargo.toml @@ -9,6 +9,7 @@ bon.workspace = true cgmath.workspace = true collision.workspace = true euclid.workspace = true +float-cmp.workspace = true log.workspace = true rand = { workspace = true, features = ["std_rng"] } rayon.workspace = true @@ -17,5 +18,4 @@ tracing = { workspace = true, features = ["log"] } [dev-dependencies] anyhow.workspace = true env_logger.workspace = true -float-cmp.workspace = true svg.workspace = true diff --git a/raydeon/examples/cube.rs b/raydeon/examples/cube.rs index f166346..618a466 100644 --- a/raydeon/examples/cube.rs +++ b/raydeon/examples/cube.rs @@ -8,10 +8,12 @@ fn main() { .init(); let scene = Scene::new() - .geometry(vec![Arc::new(AxisAlignedCuboid::new( - WVec3::new(-1.0, -1.0, -1.0), - WVec3::new(1.0, 1.0, 1.0), - ))]) + .geometry(vec![Arc::new( + AxisAlignedCuboid::new() + .min((-1.0, -1.0, -1.0)) + .max((1.0, 1.0, 1.0)) + .build(), + )]) .construct(); let eye = WPoint3::new(4.0, 3.0, 2.0); diff --git a/raydeon/examples/geom_perf.rs b/raydeon/examples/geom_perf.rs index 6f13f59..a08c29d 100644 --- a/raydeon/examples/geom_perf.rs +++ b/raydeon/examples/geom_perf.rs @@ -1,7 +1,7 @@ use std::sync::Arc; use raydeon::shapes::AxisAlignedCuboid; -use raydeon::{Camera, Scene, Shape, WPoint3, WVec3, WorldSpace}; +use raydeon::{Camera, Scene, Shape, WPoint3, WVec3}; fn main() { env_logger::Builder::from_default_env() @@ -73,8 +73,8 @@ const LENGTH: usize = 100; const CELL_WIDTH: f64 = 2.0; const CELL_LENGTH: f64 = 3.0; -fn generate_scene() -> Vec>> { - let mut scene: Vec>> = Vec::new(); +fn generate_scene() -> Vec> { + let mut scene: Vec> = Vec::new(); for i in 0..WIDTH { for j in 0..LENGTH { @@ -85,10 +85,12 @@ fn generate_scene() -> Vec>> { let z1 = cell_z + 0.15; let z2 = cell_z + CELL_LENGTH - 0.15; - scene.push(Arc::new(AxisAlignedCuboid::new( - WVec3::new(x1, 0.0, z1), - WVec3::new(x2, 2.5, z2), - ))); + scene.push(Arc::new( + AxisAlignedCuboid::new() + .min((x1, 0.0, z1)) + .max((x2, 2.5, z2)) + .build(), + )); } } diff --git a/raydeon/examples/lit_cubes.rs b/raydeon/examples/lit_cubes.rs index d5fd304..5c232a4 100644 --- a/raydeon/examples/lit_cubes.rs +++ b/raydeon/examples/lit_cubes.rs @@ -11,21 +11,27 @@ fn main() { let scene = Scene::new() .geometry(vec![ - Arc::new(AxisAlignedCuboid::tagged( - (-1.0, -1.0, -1.0), - (1.0, 1.0, 1.0), - Material::new(3.0, 2.0, 2.0, 0), - )), - Arc::new(AxisAlignedCuboid::tagged( - (1.8, -1.0, -1.0), - (3.8, 1.0, 1.0), - Material::new(2.0, 2.0, 2.0, 0), - )), - Arc::new(AxisAlignedCuboid::tagged( - (-1.4, 1.8, -1.0), - (0.6, 3.8, 1.0), - Material::new(3.0, 2.0, 2.0, 0), - )), + Arc::new( + AxisAlignedCuboid::new() + .min((-1.0, -1.0, -1.0)) + .max((1.0, 1.0, 1.0)) + .material(Material::new(3.0, 2.0, 2.0, 0)) + .build(), + ), + Arc::new( + AxisAlignedCuboid::new() + .min((1.8, -1.0, -1.0)) + .max((3.8, 1.0, 1.0)) + .material(Material::new(2.0, 2.0, 2.0, 0)) + .build(), + ), + Arc::new( + AxisAlignedCuboid::new() + .min((-1.4, 1.8, -1.0)) + .max((0.6, 3.8, 1.0)) + .material(Material::new(3.0, 2.0, 2.0, 0)) + .build(), + ), ]) .lighting( SceneLighting::new() diff --git a/raydeon/examples/triangles.rs b/raydeon/examples/triangles.rs index 2453f60..3ec8fe4 100644 --- a/raydeon/examples/triangles.rs +++ b/raydeon/examples/triangles.rs @@ -10,16 +10,20 @@ fn main() { let scene = Scene::new() .geometry(vec![ - Arc::new(Triangle::new( - WPoint3::new(0.0, 0.0, 0.0), - WPoint3::new(0.0, 0.0, 1.0), - WPoint3::new(1.0, 0.0, 1.0), - )), - Arc::new(Triangle::new( - WPoint3::new(0.25, 0.25, 0.0), - WPoint3::new(0.0, 0.25, 1.0), - WPoint3::new(-0.65, 0.25, 1.0), - )), + Arc::new( + Triangle::new() + .v0((0.0, 0.0, 0.0)) + .v1((0.0, 0.0, 1.0)) + .v2((1.0, 0.0, 1.0)) + .build(), + ), + Arc::new( + Triangle::new() + .v0((0.25, 0.25, 0.0)) + .v1((0.0, 0.25, 1.0)) + .v2((-0.65, 0.25, 1.0)) + .build(), + ), ]) .construct(); diff --git a/raydeon/src/bvh.rs b/raydeon/src/bvh.rs index 92b0b1f..e8a0e99 100644 --- a/raydeon/src/bvh.rs +++ b/raydeon/src/bvh.rs @@ -1,20 +1,17 @@ -use crate::{CollisionGeometry, HitData, PathMeta, Ray, Shape, WorldSpace, AABB3}; +use crate::{CollisionGeometry, HitData, Ray, Shape, WorldSpace, AABB3}; use euclid::Point3D; use rayon::prelude::*; use std::sync::Arc; use tracing::info; #[derive(Debug, Clone)] -pub(crate) struct Collidable { - shape: Arc>, - collision: Arc>, +pub(crate) struct Collidable { + shape: Arc, + collision: Arc, } -impl Collidable { - pub(crate) fn new( - shape: Arc>, - collision: Arc>, - ) -> Self { +impl Collidable { + pub(crate) fn new(shape: Arc, collision: Arc) -> Self { Self { shape, collision } } } @@ -27,22 +24,14 @@ pub(crate) enum Axis { } #[derive(Debug)] -pub(crate) struct BVHTree -where - Space: Copy + Send + Sync + Sized + std::fmt::Debug + 'static, - P: PathMeta, -{ - aabb: AABB3, - root: Option>, - unbounded: Vec>, +pub(crate) struct BVHTree { + aabb: AABB3, + root: Option, + unbounded: Vec, } -impl BVHTree -where - Space: Copy + Send + Sync + Sized + std::fmt::Debug + 'static, - P: PathMeta, -{ - pub(crate) fn new(collidables: &[Collidable]) -> Self { +impl BVHTree { + pub(crate) fn new(collidables: &[Collidable]) -> Self { info!( "Creating Bounded Volume Hierarchy for {} shapes", collidables.len() @@ -74,8 +63,8 @@ where } } -impl BVHTree { - pub(crate) fn intersects(&self, ray: Ray) -> Option<(HitData, Arc>)> { +impl BVHTree { + pub(crate) fn intersects(&self, ray: Ray) -> Option<(HitData, Arc)> { vec![ self.intersects_bounded_volume(ray), self.intersects_unbounded_volume(ray), @@ -85,10 +74,7 @@ impl BVHTree { .min_by(|(hit1, _), (hit2, _)| hit1.dist_to.partial_cmp(&hit2.dist_to).unwrap()) } - fn intersects_bounded_volume( - &self, - ray: Ray, - ) -> Option<(HitData, Arc>)> { + fn intersects_bounded_volume(&self, ray: Ray) -> Option<(HitData, Arc)> { let (tmin, tmax) = bounding_box_intersects(self.aabb, ray); if tmax < tmin || tmax <= 0.0 { None @@ -99,10 +85,7 @@ impl BVHTree { } } - fn intersects_unbounded_volume( - &self, - ray: Ray, - ) -> Option<(HitData, Arc>)> { + fn intersects_unbounded_volume(&self, ray: Ray) -> Option<(HitData, Arc)> { self.unbounded .iter() .filter_map(|collidable| { @@ -116,34 +99,21 @@ impl BVHTree { } #[derive(Debug)] -enum Node -where - Space: Copy + Send + Sync + Sized + std::fmt::Debug + 'static, - P: PathMeta, -{ - Parent(ParentNode), - Leaf(LeafNode), +enum Node { + Parent(ParentNode), + Leaf(LeafNode), } #[derive(Debug)] -struct ParentNode -where - Space: Copy + Send + Sync + Sized + std::fmt::Debug + 'static, - P: PathMeta, -{ +struct ParentNode { axis: Axis, point: f64, - left: Box>, - right: Box>, + left: Box, + right: Box, } -impl ParentNode { - fn intersects( - &self, - ray: Ray, - tmin: f64, - tmax: f64, - ) -> Option<(HitData, Arc>)> { +impl ParentNode { + fn intersects(&self, ray: Ray, tmin: f64, tmax: f64) -> Option<(HitData, Arc)> { let rp: f64; let rd: f64; match self.axis { @@ -194,30 +164,14 @@ impl ParentNode { } #[derive(Debug)] -struct LeafNode -where - Space: Copy + Send + Sync + Sized + std::fmt::Debug + 'static, - P: PathMeta, -{ - shapes: Vec>>, +struct LeafNode { + shapes: Vec>, } -type PartitionedSegments = ( - Vec>>, - Vec>>, -); - -impl LeafNode -where - Space: Copy + Send + Sync + Sized + std::fmt::Debug + 'static, - P: PathMeta, -{ - fn partition( - &self, - best: u64, - best_axis: Axis, - best_point: f64, - ) -> PartitionedSegments { +type PartitionedSegments = (Vec>, Vec>); + +impl LeafNode { + fn partition(&self, best: u64, best_axis: Axis, best_point: f64) -> PartitionedSegments { let mut left = Vec::with_capacity(best as usize); let mut right = Vec::with_capacity(best as usize); for shape in &self.shapes { @@ -252,8 +206,8 @@ where } } -impl LeafNode { - fn intersects(&self, ray: Ray) -> Option<(HitData, Arc>)> { +impl LeafNode { + fn intersects(&self, ray: Ray) -> Option<(HitData, Arc)> { self.shapes .iter() .filter_map(|shape| { @@ -267,12 +221,8 @@ impl LeafNode { } } -impl Node -where - Space: Copy + Send + Sync + Sized + std::fmt::Debug + 'static, - P: PathMeta, -{ - fn new(shapes: Vec>>) -> (Self, usize) { +impl Node { + fn new(shapes: Vec>) -> (Self, usize) { let mut node = Self::Leaf(LeafNode { shapes }); let depth = node.split(); (node, depth + 1) @@ -345,13 +295,8 @@ where } } -impl Node { - fn intersects( - &self, - ray: Ray, - tmin: f64, - tmax: f64, - ) -> Option<(HitData, Arc>)> { +impl Node { + fn intersects(&self, ray: Ray, tmin: f64, tmax: f64) -> Option<(HitData, Arc)> { match self { Self::Parent(parent_node) => parent_node.intersects(ray, tmin, tmax), Self::Leaf(leaf_node) => leaf_node.intersects(ray), @@ -360,20 +305,12 @@ impl Node { } #[derive(Debug)] -struct BoundedShape -where - Space: Copy + Send + Sync + Sized + std::fmt::Debug + 'static, - P: PathMeta, -{ - collidable: Collidable, - aabb: AABB3, +struct BoundedShape { + collidable: Collidable, + aabb: AABB3, } -fn bounding_box_for_shapes(shapes: &[Arc>]) -> AABB3 -where - Space: Copy + Send + Sync + Sized + std::fmt::Debug + 'static, - P: PathMeta, -{ +fn bounding_box_for_shapes(shapes: &[Arc]) -> AABB3 { let aabb = AABB3::new(Point3D::splat(f64::MAX), Point3D::splat(f64::MIN)); let bounding_boxes = shapes.iter().map(|shape| shape.aabb).collect::>(); @@ -388,10 +325,7 @@ where ) } -fn partition_bounding_box(axis: Axis, aabb: AABB3, point: f64) -> (bool, bool) -where - Space: Copy + Send + Sync + Sized + std::fmt::Debug + 'static, -{ +fn partition_bounding_box(axis: Axis, aabb: AABB3, point: f64) -> (bool, bool) { match axis { Axis::X => (aabb.min.x <= point, aabb.max.x >= point), Axis::Y => (aabb.min.y <= point, aabb.max.y >= point), diff --git a/raydeon/src/camera.rs b/raydeon/src/camera.rs index 78508e4..6d9f7b7 100644 --- a/raydeon/src/camera.rs +++ b/raydeon/src/camera.rs @@ -67,10 +67,10 @@ impl Camera { } /// Chops a line segment into subsegments based on distance from camera - pub fn chop_segment<'a, P: PathMeta>( + pub fn chop_segment<'a>( &self, - segment: &'a LineSegment3D, - ) -> Option> { + segment: &'a LineSegment3D, + ) -> Option> { let p1 = segment.p1().to_vector(); let p2 = segment.p2().to_vector(); diff --git a/raydeon/src/lib.rs b/raydeon/src/lib.rs index f49c547..7474813 100644 --- a/raydeon/src/lib.rs +++ b/raydeon/src/lib.rs @@ -14,11 +14,10 @@ use std::sync::Arc; pub use camera::{Camera, CameraOptions}; pub use lights::Light; pub use material::Material; -pub use path::{LineSegment3D, PathMeta}; +pub use path::LineSegment3D; pub use ray::{HitData, Ray}; pub use scene::{Scene, SceneGeometry, SceneLighting}; -#[cfg(test)] pub(crate) static EPSILON: f64 = 0.004; #[derive(Debug, Copy, Clone)] @@ -48,20 +47,13 @@ pub type WCTransform = Transform3; pub type WWTransform = Transform3; pub type CCTransform = Transform3; -pub trait Shape: Send + Sync + std::fmt::Debug -where - Space: Sized + Send + Sync + std::fmt::Debug + Copy + Clone, - Meta: PathMeta, -{ - fn collision_geometry(&self) -> Option>>>; - fn metadata(&self) -> Meta; - fn paths(&self, cam: &Camera) -> Vec>; +pub trait Shape: Send + Sync + std::fmt::Debug { + fn collision_geometry(&self) -> Option>>; + fn metadata(&self) -> Material; + fn paths(&self, cam: &Camera) -> Vec>; } -pub trait CollisionGeometry: Send + Sync + std::fmt::Debug -where - Space: Sized + Send + Sync + std::fmt::Debug + Copy + Clone, -{ +pub trait CollisionGeometry: Send + Sync + std::fmt::Debug { fn hit_by(&self, ray: &Ray) -> Option; - fn bounding_box(&self) -> Option>; + fn bounding_box(&self) -> Option>; } diff --git a/raydeon/src/lights.rs b/raydeon/src/lights.rs index c767696..6b4373c 100644 --- a/raydeon/src/lights.rs +++ b/raydeon/src/lights.rs @@ -3,12 +3,8 @@ use material::Material; use crate::*; pub trait Light: std::fmt::Debug + Send + Sync + 'static { - fn compute_illumination( - &self, - scene: &Scene, - hitpoint: HitData, - shape: &Arc>, - ) -> f64; + fn compute_illumination(&self, scene: &Scene, hitpoint: HitData, shape: &Arc) + -> f64; } #[derive(Debug, Copy, Clone, Default)] @@ -25,9 +21,9 @@ pub struct PointLight { impl Light for PointLight { fn compute_illumination( &self, - scene: &Scene, + scene: &Scene, hitpoint: HitData, - shape: &Arc>, + shape: &Arc, ) -> f64 { let _light_hitpoint = match self.light_hitpoint_for_hit(scene, hitpoint, shape) { Some(hit) => hit, @@ -125,9 +121,9 @@ impl PointLight { fn light_hitpoint_for_hit( &self, - scene: &Scene, + scene: &Scene, hitpoint: HitData, - shape: &Arc>, + shape: &Arc, ) -> Option { let to_light = (self.position - hitpoint.hit_point).normalize(); if to_light.dot(hitpoint.normal) < 0.0 { diff --git a/raydeon/src/material.rs b/raydeon/src/material.rs index 6a0eafd..d0c675f 100644 --- a/raydeon/src/material.rs +++ b/raydeon/src/material.rs @@ -1,3 +1,6 @@ +use crate::EPSILON; +use float_cmp::{approx_eq, ApproxEq}; + #[derive(Debug, Clone, Copy, Default)] pub struct Material { pub diffuse: f64, @@ -16,3 +19,26 @@ impl Material { } } } + +impl PartialEq for Material { + fn eq(&self, other: &Self) -> bool { + approx_eq!(&Material, self, other, epsilon = EPSILON) + } +} + +impl ApproxEq for &Material { + type Margin = float_cmp::F64Margin; + + fn approx_eq>(self, other: Self, margin: M) -> bool { + let margin = margin.into(); + approx_eq!(f64, self.diffuse, other.diffuse, epsilon = margin.epsilon) + && approx_eq!(f64, self.specular, other.specular, epsilon = margin.epsilon) + && approx_eq!( + f64, + self.shininess, + other.shininess, + epsilon = margin.epsilon + ) + && self.tag == other.tag + } +} diff --git a/raydeon/src/path.rs b/raydeon/src/path.rs index 2a9fd2c..df0ae76 100644 --- a/raydeon/src/path.rs +++ b/raydeon/src/path.rs @@ -1,44 +1,41 @@ +use crate::Material; use euclid::*; use std::collections::{BTreeSet, HashSet}; -/// Trait over metadata associated with each segment. -/// -/// This can be used to associate material data or other arbtirary information to paths for -/// post-processing. -pub trait PathMeta: Clone + std::fmt::Debug + Send + Sync + 'static {} -impl

PathMeta for P where P: Clone + std::fmt::Debug + Send + Sync + 'static {} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct NoMetadata; - #[derive(Debug, Copy, Clone)] -pub struct LineSegment3D +pub struct LineSegment3D where Space: Copy + Clone + std::fmt::Debug, - Metadata: PathMeta, { p1: Point3D, p2: Point3D, norm_dir: Vector3D, length: f64, - meta: Metadata, + material: Option, } -impl LineSegment3D +impl LineSegment3D where Space: Copy + Clone + std::fmt::Debug, { - pub fn new(p1: Point3D, p2: Point3D) -> Self { - Self::tagged(p1, p2, NoMetadata) + pub fn new( + p1: Point3D, + p2: Point3D, + material: Option, + ) -> Self { + let dir = p2 - p1; + let length = dir.length(); + let norm_dir = dir.normalize(); + Self { + p1, + p2, + length, + norm_dir, + material, + } } -} -impl LineSegment3D -where - Space: Copy + Clone + std::fmt::Debug, - Metadata: PathMeta, -{ - pub fn tagged(p1: Point3D, p2: Point3D, meta: Metadata) -> Self { + pub fn new_segment(p1: Point3D, p2: Point3D) -> Self { let dir = p2 - p1; let length = dir.length(); let norm_dir = dir.normalize(); @@ -47,10 +44,15 @@ where p2, length, norm_dir, - meta, + material: None, } } + pub fn with_material(mut self, material: Material) -> Self { + self.material = Some(material); + self + } + pub fn p1(&self) -> Point3D { self.p1 } @@ -64,8 +66,8 @@ where self.p1 + (self.p2 - self.p1) / 2.0 } - pub fn meta(&self) -> &Metadata { - &self.meta + pub fn material(&self) -> Material { + self.material.unwrap_or_default() } #[must_use] @@ -78,11 +80,16 @@ where self.length } - pub fn cast_unit(self) -> LineSegment3D + pub fn cast_unit(self) -> LineSegment3D where U: Copy + Clone + std::fmt::Debug, { - LineSegment3D::tagged(self.p1.cast_unit(), self.p2.cast_unit(), self.meta) + let slf = LineSegment3D::new_segment(self.p1.cast_unit(), self.p2.cast_unit()); + if let Some(material) = self.material { + slf.with_material(material) + } else { + slf + } } pub fn xy(self) -> LineSegment2D { @@ -100,47 +107,45 @@ where pub fn transform( &self, transformation: &Transform3D, - ) -> Option> + ) -> Option> where Dst: Copy + Clone + std::fmt::Debug, { let (p1, p2) = (self.p1, self.p2); let p1t = transformation.transform_point3d(p1); let p2t = transformation.transform_point3d(p2); - p1t.and_then(|p1| p2t.map(|p2| LineSegment3D::tagged(p1, p2, self.meta.clone()))) + p1t.and_then(|p1| p2t.map(|p2| LineSegment3D::new(p1, p2, self.material))) } pub fn transform_without_metadata( &self, transformation: &Transform3D, - ) -> Option> + ) -> Option> where Dst: Copy + Clone + std::fmt::Debug, { let (p1, p2) = (self.p1, self.p2); let p1t = transformation.transform_point3d(p1); let p2t = transformation.transform_point3d(p2); - p1t.and_then(|p1| p2t.map(|p2| LineSegment3D::tagged(p1, p2, NoMetadata))) + p1t.and_then(|p1| p2t.map(|p2| LineSegment3D::new_segment(p1, p2))) } } /// Created when a segment is chopped into several smaller pieces -pub struct SlicedSegment3D<'parent, Space, Metadata> +pub struct SlicedSegment3D<'parent, Space> where Space: Copy + Clone + std::fmt::Debug, - Metadata: PathMeta, { num_chops: usize, included: BTreeSet, - parent: &'parent LineSegment3D, + parent: &'parent LineSegment3D, } -impl<'parent, Space, Metadata> SlicedSegment3D<'parent, Space, Metadata> +impl<'parent, Space> SlicedSegment3D<'parent, Space> where Space: Copy + Clone + std::fmt::Debug, - Metadata: PathMeta, { - pub fn new(num_chops: usize, parent: &'parent LineSegment3D) -> Self { + pub fn new(num_chops: usize, parent: &'parent LineSegment3D) -> Self { let included = (0..num_chops).collect(); Self { num_chops, @@ -157,14 +162,14 @@ where self.included.len() } - fn get_subsegment(&self, ndx: usize) -> LineSegment3D { + fn get_subsegment(&self, ndx: usize) -> LineSegment3D { let segment_vec = self.parent.dir() * self.subsegment_len(); let start = self.parent.p1 + segment_vec * (ndx as f64); let end = start + segment_vec; - LineSegment3D::tagged(start, end, NoMetadata) + LineSegment3D::new_segment(start, end) } - pub fn subsegments(&self) -> impl Iterator> + '_ { + pub fn subsegments(&self) -> impl Iterator> + '_ { self.included .iter() .map(move |ndx| self.get_subsegment(*ndx)) @@ -174,15 +179,12 @@ where self.included.remove(&ndx); } - pub fn join_slices(&self) -> Vec> { + pub fn join_slices(&self) -> Vec> { self.join_slices_with_forgiveness(0) } /// Joins slices, ignoring gaps of size `forgiveness` or smaller - pub fn join_slices_with_forgiveness( - &self, - forgiveness: usize, - ) -> Vec> { + pub fn join_slices_with_forgiveness(&self, forgiveness: usize) -> Vec> { if self.included.is_empty() { return Vec::new(); } @@ -232,7 +234,7 @@ where .map(|ndx_group| { let start = self.get_subsegment(*ndx_group.start()).p1; let end = self.get_subsegment(*ndx_group.end()).p2; - LineSegment3D::tagged(start, end, self.parent.meta.clone()) + LineSegment3D::new(start, end, self.parent.material) }) .collect() } diff --git a/raydeon/src/scene.rs b/raydeon/src/scene.rs index eb31087..418727a 100644 --- a/raydeon/src/scene.rs +++ b/raydeon/src/scene.rs @@ -2,7 +2,6 @@ use bon::Builder; use bvh::{BVHTree, Collidable}; use collision::Continuous; use euclid::{Point2D, Vector2D}; -use material::Material; use path::{LineSegment2D, SlicedSegment3D}; use rand::distributions::Distribution; use rand::SeedableRng; @@ -14,44 +13,44 @@ use crate::*; #[derive(Debug, Builder)] #[builder(start_fn(name = new), finish_fn(name = construct))] -pub struct Scene { +pub struct Scene { #[builder(into)] - geometry: SceneGeometry

, + geometry: SceneGeometry, #[builder(into, default)] lighting: SceneLighting, } #[derive(Debug)] -pub struct SceneGeometry { - geometry: Vec>>, - bvh: BVHTree, +pub struct SceneGeometry { + geometry: Vec>, + bvh: BVHTree, } -impl SceneGeometry

{ - pub fn new() -> SceneGeometry

{ +impl SceneGeometry { + pub fn new() -> SceneGeometry { Default::default() } - pub fn with_geometry(mut self, geometry: Vec>>) -> Self { + pub fn with_geometry(mut self, geometry: Vec>) -> Self { let bvh = Self::create_bvh(&geometry); self.geometry = geometry; self.bvh = bvh; self } - pub fn push_geometry(mut self, geometry: Arc>) -> Self { + pub fn push_geometry(mut self, geometry: Arc) -> Self { self.geometry.push(geometry); self.bvh = Self::create_bvh(&self.geometry); self } - pub fn concat_geometry(mut self, geometry: &[Arc>]) -> Self { + pub fn concat_geometry(mut self, geometry: &[Arc]) -> Self { self.geometry.extend_from_slice(geometry); self.bvh = Self::create_bvh(&self.geometry); self } - fn create_bvh(geometry: &[Arc>]) -> BVHTree { + fn create_bvh(geometry: &[Arc]) -> BVHTree { let collision_geometry: Vec<_> = geometry .iter() .filter_map(|s| { @@ -69,7 +68,7 @@ impl SceneGeometry

{ } } -impl Default for SceneGeometry

{ +impl Default for SceneGeometry { fn default() -> Self { Self { geometry: Vec::new(), @@ -78,19 +77,19 @@ impl Default for SceneGeometry

{ } } -impl + 'static> From>> for SceneGeometry

{ +impl From>> for SceneGeometry { fn from(geometry: Vec>) -> Self { SceneGeometry::new().with_geometry( geometry .into_iter() - .map(|s| s as Arc>) + .map(|s| s as Arc) .collect::>(), ) } } -impl From>>> for SceneGeometry

{ - fn from(geometry: Vec>>) -> Self { +impl From>> for SceneGeometry { + fn from(geometry: Vec>) -> Self { SceneGeometry::new().with_geometry(geometry) } } @@ -143,13 +142,13 @@ impl From>> for SceneLighting { } } -impl Scene

{ - pub fn attach_camera(&self, camera: Camera) -> SceneCamera

{ +impl Scene { + pub fn attach_camera(&self, camera: Camera) -> SceneCamera { SceneCamera::new(camera, self) } /// Find's the closest intersection point to geometry in the scene, if any - pub(crate) fn intersects(&self, ray: Ray) -> Option<(HitData, Arc>)> { + pub(crate) fn intersects(&self, ray: Ray) -> Option<(HitData, Arc)> { self.geometry.bvh.intersects(ray) } @@ -169,14 +168,14 @@ impl Scene

{ } #[derive(Debug)] -pub struct SceneCamera<'s, P: PathMeta> { +pub struct SceneCamera<'s> { camera: Camera, - scene: &'s Scene

, + scene: &'s Scene, seed: Option, } -impl<'a, P: PathMeta> SceneCamera<'a, P> { - pub fn new(camera: Camera, scene: &'a Scene

) -> Self { +impl<'a> SceneCamera<'a> { + pub fn new(camera: Camera, scene: &'a Scene) -> Self { SceneCamera { camera, scene, @@ -189,14 +188,14 @@ impl<'a, P: PathMeta> SceneCamera<'a, P> { self } - fn clip_filter(&self, path: &LineSegment3D) -> bool { + fn clip_filter(&self, path: &LineSegment3D) -> bool { self.scene .visible(self.camera.observation.eye, path.midpoint()) } pub fn render(&self) -> Vec> { info!("Querying geometry for subpaths"); - let parent_paths: Vec> = self + let parent_paths: Vec> = self .scene .geometry .geometry @@ -209,7 +208,7 @@ impl<'a, P: PathMeta> SceneCamera<'a, P> { parent_paths.len() ); - let mut paths: Vec> = parent_paths + let mut paths: Vec> = parent_paths .iter() .filter_map(|path| self.camera.chop_segment(path)) .collect(); @@ -262,7 +261,7 @@ pub struct LitScene { pub hatch_paths: Vec>, } -impl<'a> SceneCamera<'a, Material> { +impl<'a> SceneCamera<'a> { pub fn render_with_lighting(&self) -> LitScene { let geometry_paths = self.render(); let mut rng = match self.seed { @@ -299,7 +298,7 @@ impl<'a> SceneCamera<'a, Material> { ) -> Vec> { let segments = segments .iter() - .map(|segment| LineSegment3D::new(segment.p1.to_3d(), segment.p2.to_3d())) + .map(|segment| LineSegment3D::new_segment(segment.p1.to_3d(), segment.p2.to_3d())) .collect::>(); let mut split_segments = segments .iter() diff --git a/raydeon/src/shapes/aacuboid.rs b/raydeon/src/shapes/aacuboid.rs index 409ce82..5993546 100644 --- a/raydeon/src/shapes/aacuboid.rs +++ b/raydeon/src/shapes/aacuboid.rs @@ -1,54 +1,44 @@ //! Provides basic drawing and collision for axis-aligned cuboids. +use bon::Builder; use core::f64; use euclid::Vector3D; use std::sync::Arc; use crate::path::LineSegment3D; use crate::{ - Camera, CollisionGeometry, HitData, PathMeta, Ray, Shape, WPoint3, WVec3, WorldSpace, AABB3, + Camera, CollisionGeometry, HitData, Material, Ray, Shape, WPoint3, WVec3, WorldSpace, AABB3, }; -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, Builder)] +#[builder(start_fn(name = new))] #[cfg_attr(test, derive(PartialEq))] -pub struct AxisAlignedCuboid

-where - P: PathMeta, -{ +pub struct AxisAlignedCuboid { + #[builder(into)] pub min: WVec3, + #[builder(into)] pub max: WVec3, - pub meta: P, + pub material: Option, } -impl AxisAlignedCuboid { - pub fn new(min: impl Into, max: impl Into) -> AxisAlignedCuboid { - Self::tagged(min, max, 0) - } -} - -impl AxisAlignedCuboid

{ - pub fn tagged(min: impl Into, max: impl Into, meta: P) -> AxisAlignedCuboid

{ - let min = min.into(); - let max = max.into(); - AxisAlignedCuboid { min, max, meta } - } -} - -impl From> for AxisAlignedCuboid { +impl From> for AxisAlignedCuboid { fn from(value: AABB3) -> Self { - Self::new(value.min.to_vector(), value.max.to_vector()) + Self::new() + .min(value.min.to_vector()) + .max(value.max.to_vector()) + .build() } } -impl Shape for AxisAlignedCuboid

{ - fn metadata(&self) -> P { - self.meta.clone() +impl Shape for AxisAlignedCuboid { + fn metadata(&self) -> Material { + self.material.unwrap_or_default() } - fn collision_geometry(&self) -> Option>>> { - Some(vec![Arc::new(self.clone())]) + fn collision_geometry(&self) -> Option>> { + Some(vec![Arc::new(*self)]) } - fn paths(&self, _cam: &Camera) -> Vec> { + fn paths(&self, _cam: &Camera) -> Vec> { let expand = (self.max - self.min).normalize() * 0.003; let pathmin = self.min - expand; let pathmax = self.max + expand; @@ -67,23 +57,23 @@ impl Shape for AxisAlignedCuboid

{ let p8 = WPoint3::new(x1, y2, z2); vec![ - LineSegment3D::tagged(p1, p2, self.meta.clone()), - LineSegment3D::tagged(p2, p3, self.meta.clone()), - LineSegment3D::tagged(p3, p4, self.meta.clone()), - LineSegment3D::tagged(p4, p1, self.meta.clone()), - LineSegment3D::tagged(p5, p6, self.meta.clone()), - LineSegment3D::tagged(p6, p7, self.meta.clone()), - LineSegment3D::tagged(p7, p8, self.meta.clone()), - LineSegment3D::tagged(p8, p5, self.meta.clone()), - LineSegment3D::tagged(p1, p5, self.meta.clone()), - LineSegment3D::tagged(p2, p6, self.meta.clone()), - LineSegment3D::tagged(p3, p7, self.meta.clone()), - LineSegment3D::tagged(p4, p8, self.meta.clone()), + LineSegment3D::new(p1, p2, self.material), + LineSegment3D::new(p2, p3, self.material), + LineSegment3D::new(p3, p4, self.material), + LineSegment3D::new(p4, p1, self.material), + LineSegment3D::new(p5, p6, self.material), + LineSegment3D::new(p6, p7, self.material), + LineSegment3D::new(p7, p8, self.material), + LineSegment3D::new(p8, p5, self.material), + LineSegment3D::new(p1, p5, self.material), + LineSegment3D::new(p2, p6, self.material), + LineSegment3D::new(p3, p7, self.material), + LineSegment3D::new(p4, p8, self.material), ] } } -impl CollisionGeometry for AxisAlignedCuboid

{ +impl CollisionGeometry for AxisAlignedCuboid { fn hit_by(&self, ray: &Ray) -> Option { let dir_inv = Vector3D::new(1.0, 1.0, 1.0).component_div(ray.dir); let t1: Vector3D = @@ -139,7 +129,10 @@ mod test { #[test] fn test_rectp_hit_by() { - let prism1 = AxisAlignedCuboid::new(WVec3::new(0.0, 0.0, 0.0), WVec3::new(1.0, 1.0, 1.0)); + let prism1 = AxisAlignedCuboid::new() + .min((0.0, 0.0, 0.0)) + .max((1.0, 1.0, 1.0)) + .build(); let ray1 = Ray::new(WPoint3::new(-1.0, 0.5, 0.5), WVec3::new(1.0, 0.0, 0.0)); diff --git a/raydeon/src/shapes/plane.rs b/raydeon/src/shapes/plane.rs index 170e27a..909aaa8 100644 --- a/raydeon/src/shapes/plane.rs +++ b/raydeon/src/shapes/plane.rs @@ -1,23 +1,21 @@ //! Provides collision for 3D planes. -use crate::{CollisionGeometry, HitData, Ray, WPoint3, WVec3, WorldSpace}; +use crate::{CollisionGeometry, HitData, Ray, WPoint3, WVec3}; +use bon::Builder; -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, Builder)] +#[builder(start_fn(name = new))] #[cfg_attr(test, derive(PartialEq))] /// An infinite plane in 3D space. pub struct Plane { /// An arbitrary point in space which exists on the plane. + #[builder(into)] pub point: WPoint3, /// A normal vector to the plane. + #[builder(into)] pub normal: WVec3, } -impl Plane { - pub fn new(point: WPoint3, normal: WVec3) -> Plane { - Plane { point, normal } - } -} - -impl CollisionGeometry for Plane { +impl CollisionGeometry for Plane { fn hit_by(&self, ray: &Ray) -> Option { let rdn = ray.dir.dot(self.normal); if rdn == 0.0 { @@ -46,7 +44,10 @@ mod test { #[test] fn test_hit_by() { - let plane1 = Plane::new(WPoint3::new(1.0, 0.0, 0.0), WVec3::new(-1.0, 0.0, 0.0)); + let plane1 = Plane::new() + .point((1.0, 0.0, 0.0)) + .normal((-1.0, 0.0, 0.0)) + .build(); assert_eq!( plane1.hit_by(&Ray::new( diff --git a/raydeon/src/shapes/quad.rs b/raydeon/src/shapes/quad.rs index f45aa29..40e31cc 100644 --- a/raydeon/src/shapes/quad.rs +++ b/raydeon/src/shapes/quad.rs @@ -1,58 +1,59 @@ +use bon::Builder; use std::sync::Arc; use super::Triangle; use crate::path::LineSegment3D; -use crate::{Camera, CollisionGeometry, PathMeta, Shape, WPoint3, WVec3, WorldSpace}; +use crate::{Camera, CollisionGeometry, Material, Shape, WPoint3, WVec3, WorldSpace}; -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, Builder)] +#[builder(start_fn(name = new))] #[cfg_attr(test, derive(PartialEq))] -pub struct Quad { +pub struct Quad { + #[builder(into)] pub origin: WPoint3, + + #[builder(with = |basis: impl Into<[WVec3; 2]>| { + let basis = basis.into(); + [basis[0].normalize(), basis[1].normalize()] + })] pub basis: [WVec3; 2], pub dims: [f64; 2], - pub verts: [WPoint3; 4], - meta: P, -} + material: Option, -impl Quad { - pub fn new(origin: WPoint3, basis: [WVec3; 2], dims: [f64; 2]) -> Self { - Self::tagged(origin, basis, dims, 0) - } -} - -impl Quad

{ - pub fn tagged(origin: WPoint3, mut basis: [WVec3; 2], dims: [f64; 2], meta: P) -> Quad

{ - basis[0] = basis[0].normalize(); - basis[1] = basis[1].normalize(); - let verts = [ + #[builder(skip = [ origin, origin + basis[0] * dims[0], origin + basis[0] * dims[0] + basis[1] * dims[1], origin + basis[1] * dims[1], - ]; - Quad { - origin, - basis, - dims, - verts, - meta, - } - } + ])] + pub verts: [WPoint3; 4], } -impl Shape for Quad

{ - fn metadata(&self) -> P { - self.meta.clone() +impl Shape for Quad { + fn metadata(&self) -> Material { + self.material.unwrap_or_default() } - fn collision_geometry(&self) -> Option>>> { + fn collision_geometry(&self) -> Option>> { Some(vec![ - Arc::new(Triangle::new(self.verts[0], self.verts[1], self.verts[3])), - Arc::new(Triangle::new(self.verts[1], self.verts[2], self.verts[3])), + Arc::new( + Triangle::new() + .v0(self.verts[0]) + .v1(self.verts[1]) + .v2(self.verts[3]) + .build(), + ), + Arc::new( + Triangle::new() + .v0(self.verts[1]) + .v1(self.verts[2]) + .v2(self.verts[3]) + .build(), + ), ]) } - fn paths(&self, _cam: &Camera) -> Vec> { + fn paths(&self, _cam: &Camera) -> Vec> { let centroid = self .verts .into_iter() @@ -67,10 +68,10 @@ impl Shape for Quad

{ .collect::>(); vec![ - LineSegment3D::tagged(v[0], v[1], self.meta.clone()), - LineSegment3D::tagged(v[1], v[2], self.meta.clone()), - LineSegment3D::tagged(v[2], v[3], self.meta.clone()), - LineSegment3D::tagged(v[3], v[0], self.meta.clone()), + LineSegment3D::new(v[0], v[1], self.material), + LineSegment3D::new(v[1], v[2], self.material), + LineSegment3D::new(v[2], v[3], self.material), + LineSegment3D::new(v[3], v[0], self.material), ] } } diff --git a/raydeon/src/shapes/sphere.rs b/raydeon/src/shapes/sphere.rs index e0769da..3501d57 100644 --- a/raydeon/src/shapes/sphere.rs +++ b/raydeon/src/shapes/sphere.rs @@ -1,30 +1,23 @@ //! Provides collision for spheres. -use crate::{CollisionGeometry, HitData, Ray, WPoint3, WVec3, WorldSpace}; +use crate::{CollisionGeometry, HitData, Ray, WPoint3, WVec3}; +use bon::{builder, Builder}; -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, Builder)] +#[builder(start_fn(name = new))] #[cfg_attr(test, derive(PartialEq))] /// A sphere at an arbitrary location in 3d space. pub struct Sphere { /// The location of the center of the sphere. + #[builder(into)] pub center: WPoint3, /// The radius of the sphere. pub radius: f64, /// Precomputed radius squared. + #[builder(skip = radius * radius)] radius2: f64, } -impl Sphere { - pub fn new(center: WPoint3, radius: f64) -> Sphere { - let radius2 = radius * radius; - Sphere { - center, - radius, - radius2, - } - } -} - -impl CollisionGeometry for Sphere { +impl CollisionGeometry for Sphere { fn hit_by(&self, ray: &Ray) -> Option { let l_vec = self.center - ray.point; let t_ca = l_vec.dot(ray.dir); @@ -64,7 +57,7 @@ mod test { #[test] fn test_hit_by() { - let sphere1 = Sphere::new(WPoint3::new(1.0, 0.0, 0.0), 0.5); + let sphere1 = Sphere::new().center((1.0, 0.0, 0.0)).radius(0.5).build(); assert_eq!( sphere1.hit_by(&Ray::new( @@ -94,7 +87,7 @@ mod test { None ); - let sphere2 = Sphere::new(WPoint3::new(1.0, 1.0, 0.0), 0.5); + let sphere2 = Sphere::new().center((1.0, 1.0, 0.0)).radius(0.5).build(); assert_eq!( sphere2.hit_by(&Ray::new( @@ -108,7 +101,7 @@ mod test { )) ); - let sphere3 = Sphere::new(WPoint3::new(0.0, 0.0, 0.0), 1.0); + let sphere3 = Sphere::new().center((0.0, 0.0, 0.0)).radius(1.0).build(); assert_eq!( sphere3.hit_by(&Ray::new( diff --git a/raydeon/src/shapes/triangle.rs b/raydeon/src/shapes/triangle.rs index 65c4a14..0acb759 100644 --- a/raydeon/src/shapes/triangle.rs +++ b/raydeon/src/shapes/triangle.rs @@ -1,50 +1,48 @@ //! Provides basic drawing and collision for triangles. +use bon::Builder; use std::sync::Arc; use super::plane::Plane; use crate::path::LineSegment3D; -use crate::{Camera, CollisionGeometry, HitData, PathMeta, Ray, Shape, WPoint3, WVec3, WorldSpace}; +use crate::{Camera, CollisionGeometry, HitData, Material, Ray, Shape, WPoint3, WVec3, WorldSpace}; -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, Builder)] +#[builder(start_fn(name = new))] #[cfg_attr(test, derive(PartialEq))] -pub struct Triangle { +pub struct Triangle { + #[builder(into)] + pub v0: WPoint3, + #[builder(into)] + pub v1: WPoint3, + #[builder(into)] + pub v2: WPoint3, + + #[builder(skip = [v0, v1, v2])] pub verts: [WPoint3; 3], + + #[builder(skip = [v1 - v0, v2 - v1, v0 - v2])] pub edges: [WVec3; 3], - pub plane: Plane, - pub meta: P, -} -impl Triangle { - pub fn new(v0: WPoint3, v1: WPoint3, v2: WPoint3) -> Self { - Self::tagged(v0, v1, v2, 0) - } -} + #[builder(skip = Plane::new() + .point(v0) + .normal((v1 - v0).cross(v2 - v0).normalize()) + .build() + )] + pub plane: Plane, -impl Triangle

{ - pub fn tagged(v0: WPoint3, v1: WPoint3, v2: WPoint3, meta: P) -> Triangle

{ - let verts = [v0, v1, v2]; - let edges = [v1 - v0, v2 - v1, v0 - v2]; - let normal = (v1 - v0).cross(v2 - v0).normalize(); - let plane = Plane::new(v0, normal); - Triangle { - verts, - edges, - plane, - meta, - } - } + pub material: Option, } -impl Shape for Triangle

{ - fn metadata(&self) -> P { - self.meta.clone() +impl Shape for Triangle { + fn metadata(&self) -> Material { + self.material.unwrap_or_default() } - fn collision_geometry(&self) -> Option>>> { - Some(vec![Arc::new(self.clone())]) + fn collision_geometry(&self) -> Option>> { + Some(vec![Arc::new(*self)]) } - fn paths(&self, _cam: &Camera) -> Vec> { + fn paths(&self, _cam: &Camera) -> Vec> { let v0 = self.verts[0]; let v1 = self.verts[1]; let v2 = self.verts[2]; @@ -55,14 +53,14 @@ impl Shape for Triangle

{ let v2 = v2 + (v2 - centroid).normalize() * 0.015; vec![ - LineSegment3D::tagged(v0, v1, self.meta.clone()), - LineSegment3D::tagged(v1, v2, self.meta.clone()), - LineSegment3D::tagged(v2, v0, self.meta.clone()), + LineSegment3D::new(v0, v1, self.material), + LineSegment3D::new(v1, v2, self.material), + LineSegment3D::new(v2, v0, self.material), ] } } -impl CollisionGeometry for Triangle

{ +impl CollisionGeometry for Triangle { fn hit_by(&self, ray: &Ray) -> Option { let p_hit = self.plane.hit_by(ray); @@ -115,11 +113,11 @@ mod test { #[test] fn test_tri_hit_by() { - let tri1 = Triangle::new( - WPoint3::new(0.0, 0.0, 0.0), - WPoint3::new(2.0, 0.0, 0.0), - WPoint3::new(0.0, 2.0, 0.0), - ); + let tri1 = Triangle::new() + .v0((0.0, 0.0, 0.0)) + .v1((2.0, 0.0, 0.0)) + .v2((0.0, 2.0, 0.0)) + .build(); // hits tri1 let ray1 = Ray::new(