From 995f5d262ce6f42f5821ae9279ad9700189f191e Mon Sep 17 00:00:00 2001 From: "Sean P. Kelly" Date: Tue, 26 Nov 2024 20:41:17 -0800 Subject: [PATCH] feat(pyraydeon): expose render settings through python --- pyraydeon/examples/py_sphere.py | 12 +++- pyraydeon/src/lib.rs | 10 ++- pyraydeon/src/scene.rs | 115 +++----------------------------- pyraydeon/src/shapes/mod.rs | 3 +- raydeon/examples/cube.rs | 2 +- raydeon/src/camera.rs | 29 ++++---- raydeon/src/path.rs | 90 +++++++++++++------------ raydeon/src/scene.rs | 7 +- raydeon/src/shapes/aacuboid.rs | 31 +++++---- raydeon/src/shapes/quad.rs | 10 ++- raydeon/src/shapes/triangle.rs | 6 +- 11 files changed, 119 insertions(+), 196 deletions(-) diff --git a/pyraydeon/examples/py_sphere.py b/pyraydeon/examples/py_sphere.py index b830105..e700abc 100644 --- a/pyraydeon/examples/py_sphere.py +++ b/pyraydeon/examples/py_sphere.py @@ -13,6 +13,7 @@ PointLight, Plane, LineSegment3D, + CameraOptions, ) @@ -107,7 +108,16 @@ def paths(self, cam): znear = 0.1 zfar = 100.0 -cam = Camera().look_at(eye, focus, up).perspective(fovy, width, height, znear, zfar) +render_opts = CameraOptions(pen_px_size=4.0) + +assert render_opts.hatch_slice_forgiveness == 1 + +cam = ( + Camera() + .look_at(eye, focus, up) + .perspective(fovy, width, height, znear, zfar) + .render_options(render_opts) +) paths = scene.render_with_lighting(cam, seed=5) diff --git a/pyraydeon/src/lib.rs b/pyraydeon/src/lib.rs index 991bb6c..3692d1d 100644 --- a/pyraydeon/src/lib.rs +++ b/pyraydeon/src/lib.rs @@ -2,7 +2,7 @@ use pyo3::prelude::*; macro_rules! pywrap { ($name:ident, $wraps:ty) => { - #[derive(Debug, Clone, Copy)] + #[derive(Debug, Clone)] #[pyclass(frozen)] pub(crate) struct $name(pub(crate) $wraps); @@ -22,6 +22,7 @@ macro_rules! pywrap { }; } +mod camera; mod light; mod linear; mod material; @@ -33,10 +34,15 @@ mod shapes; #[pymodule] fn pyraydeon(m: &Bound<'_, PyModule>) -> PyResult<()> { crate::linear::register(m)?; - crate::shapes::register(m)?; + + crate::camera::register(m)?; crate::scene::register(m)?; + crate::ray::register(m)?; + + crate::shapes::register(m)?; crate::material::register(m)?; crate::light::register(m)?; + Ok(()) } diff --git a/pyraydeon/src/scene.rs b/pyraydeon/src/scene.rs index 1f0c85f..f07932b 100644 --- a/pyraydeon/src/scene.rs +++ b/pyraydeon/src/scene.rs @@ -4,108 +4,12 @@ use numpy::{Ix1, PyArray, PyReadonlyArray1}; use pyo3::prelude::*; use raydeon::SceneLighting; +use crate::camera::Camera; use crate::light::PointLight; -use crate::linear::{ArbitrarySpace, Point2, Point3, Vec3}; +use crate::linear::{ArbitrarySpace, Point2, Point3}; use crate::material::Material; use crate::shapes::Geometry; -#[derive(Debug, Clone)] -#[pyclass(frozen)] -pub(crate) struct Camera(pub(crate) raydeon::Camera); - -impl ::std::ops::Deref for Camera { - type Target = raydeon::Camera; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} - -impl From for Camera { - fn from(value: raydeon::Camera) -> Self { - Self(value) - } -} - -#[pymethods] -impl Camera { - #[new] - fn new() -> Self { - raydeon::Camera::default().into() - } - - fn look_at( - &self, - eye: &Bound<'_, PyAny>, - center: &Bound<'_, PyAny>, - up: &Bound<'_, PyAny>, - ) -> PyResult { - let eye = Point3::try_from(eye)?; - let center = Vec3::try_from(center)?; - let up = Vec3::try_from(up)?; - let mut ncam = self.0.clone(); - ncam.observation = - raydeon::Camera::look_at(eye.cast_unit(), center.cast_unit(), up.cast_unit()); - Ok(ncam.into()) - } - - fn perspective(&self, fovy: f64, width: usize, height: usize, znear: f64, zfar: f64) -> Camera { - let mut ncam = self.0.clone(); - ncam.perspective = raydeon::Camera::perspective(fovy, width, height, znear, zfar); - ncam.into() - } - - #[getter] - fn eye<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray> { - PyArray::from_slice_bound(py, &self.0.observation.eye.to_array()) - } - - #[getter] - fn focus<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray> { - PyArray::from_slice_bound(py, &self.0.observation.center.to_array()) - } - - #[getter] - fn up<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray> { - PyArray::from_slice_bound(py, &self.0.observation.up.to_array()) - } - - #[getter] - fn fovy(&self) -> f64 { - self.0.perspective.fovy - } - - #[getter] - fn width(&self) -> usize { - self.0.perspective.width - } - - #[getter] - fn height(&self) -> usize { - self.0.perspective.height - } - - #[getter] - fn aspect(&self) -> f64 { - self.0.perspective.aspect - } - - #[getter] - fn znear(&self) -> f64 { - self.0.perspective.znear - } - - #[getter] - fn zfar(&self) -> f64 { - self.0.perspective.zfar - } - - fn __repr__(slf: &Bound<'_, Self>) -> PyResult { - let class_name = slf.get_type().qualname()?; - Ok(format!("{}<{:?}>", class_name, slf.borrow().0)) - } -} - #[pyclass(frozen)] pub(crate) struct Scene { scene: Arc, @@ -228,14 +132,12 @@ impl LineSegment3D { ) -> PyResult { let p1 = Point3::try_from(p1)?; let p2 = Point3::try_from(p2)?; - Ok( - raydeon::path::LineSegment3D::new( - p1.cast_unit(), - p2.cast_unit(), - material.map(|i| i.0), - ) - .into(), - ) + Ok(raydeon::path::LineSegment3D::new() + .p1(p1.cast_unit()) + .p2(p2.cast_unit()) + .maybe_material(material.map(|i| i.0)) + .build() + .into()) } #[getter] @@ -255,7 +157,6 @@ impl LineSegment3D { } pub(crate) fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { - m.add_class::()?; m.add_class::()?; m.add_class::()?; m.add_class::()?; diff --git a/pyraydeon/src/shapes/mod.rs b/pyraydeon/src/shapes/mod.rs index 43e345a..119edc4 100644 --- a/pyraydeon/src/shapes/mod.rs +++ b/pyraydeon/src/shapes/mod.rs @@ -8,9 +8,10 @@ mod primitive; pub(crate) use primitive::{AxisAlignedCuboid, Tri}; +use crate::camera::Camera; use crate::material::Material; use crate::ray::{HitData, Ray, AABB3}; -use crate::scene::{Camera, LineSegment3D}; +use crate::scene::LineSegment3D; #[derive(Debug)] enum InnerGeometry { diff --git a/raydeon/examples/cube.rs b/raydeon/examples/cube.rs index 618a466..9341046 100644 --- a/raydeon/examples/cube.rs +++ b/raydeon/examples/cube.rs @@ -29,7 +29,7 @@ fn main() { let camera = Camera::configure() .observation(Camera::look_at(eye, focus, up)) .perspective(Camera::perspective(fovy, width, height, znear, zfar)) - .render_options(CameraOptions::defaults_for_pen_px_size(4.0)) + .render_options(CameraOptions::configure().pen_px_size(4.0).build()) .build(); let paths = scene.attach_camera(camera).render(); diff --git a/raydeon/src/camera.rs b/raydeon/src/camera.rs index 6d9f7b7..127fe33 100644 --- a/raydeon/src/camera.rs +++ b/raydeon/src/camera.rs @@ -5,6 +5,13 @@ use path::SlicedSegment3D; use self::view_matrix_settings::*; use crate::*; +pub const DEFAULT_PEN_PX_SIZE: f64 = 4.0; +pub const DEFAULT_HATCH_PIXEL_SPACING_FACTOR: f64 = 2.0; +pub const DEFAULT_HATCH_PIXEL_CHOP_QUOTIENT: f64 = 3.0; +pub const DEFAULT_HATCH_SLICE_FORGIVENESS: usize = 1; +pub const DEFAULT_VERT_HATCH_BRIGHTNESS_SCALING: f64 = 0.8; +pub const DEFAULT_DIAG_HATCH_BRIGHTNESS_SCALING: f64 = 0.46; + #[derive(Debug, Clone, Builder, Default)] #[builder(start_fn(name = configure))] pub struct Camera { @@ -15,31 +22,25 @@ pub struct Camera { } #[derive(Debug, Clone, Builder)] +#[builder(start_fn(name = configure))] pub struct CameraOptions { + #[builder(default = DEFAULT_PEN_PX_SIZE)] pub pen_px_size: f64, + #[builder(default = pen_px_size * DEFAULT_HATCH_PIXEL_SPACING_FACTOR)] pub hatch_pixel_spacing: f64, + #[builder(default = pen_px_size / DEFAULT_HATCH_PIXEL_CHOP_QUOTIENT)] pub hatch_pixel_chop_factor: f64, + #[builder(default = DEFAULT_HATCH_SLICE_FORGIVENESS)] pub hatch_slice_forgiveness: usize, + #[builder(default = DEFAULT_VERT_HATCH_BRIGHTNESS_SCALING)] pub vert_hatch_brightness_scaling: f64, + #[builder(default = DEFAULT_DIAG_HATCH_BRIGHTNESS_SCALING)] pub diag_hatch_brightness_scaling: f64, } impl Default for CameraOptions { fn default() -> Self { - Self::defaults_for_pen_px_size(4.0) - } -} - -impl CameraOptions { - pub fn defaults_for_pen_px_size(px_size: f64) -> Self { - Self { - pen_px_size: px_size, - hatch_pixel_spacing: px_size * 2.0, - hatch_pixel_chop_factor: px_size / 3.0, - hatch_slice_forgiveness: 1, - vert_hatch_brightness_scaling: 0.8, - diag_hatch_brightness_scaling: 0.46, - } + Self::configure().build() } } diff --git a/raydeon/src/path.rs b/raydeon/src/path.rs index df0ae76..e2e5144 100644 --- a/raydeon/src/path.rs +++ b/raydeon/src/path.rs @@ -1,15 +1,22 @@ use crate::Material; +use bon::Builder; use euclid::*; use std::collections::{BTreeSet, HashSet}; -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, Builder)] +#[builder(start_fn(name = new))] pub struct LineSegment3D where Space: Copy + Clone + std::fmt::Debug, { + #[builder(into)] p1: Point3D, + #[builder(into)] p2: Point3D, + + #[builder(skip = (p2 - p1).normalize())] norm_dir: Vector3D, + #[builder(skip = (p2 - p1).length())] length: f64, material: Option, } @@ -18,39 +25,23 @@ impl LineSegment3D where Space: Copy + Clone + std::fmt::Debug, { - 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, - } - } - - pub fn new_segment(p1: Point3D, p2: Point3D) -> Self { - let dir = p2 - p1; - let length = dir.length(); - let norm_dir = dir.normalize(); - Self { - p1, - p2, - length, - norm_dir, - material: None, - } - } - - pub fn with_material(mut self, material: Material) -> Self { - self.material = Some(material); - self + pub fn from_points( + pairs: Vec<( + impl Into>, + impl Into>, + )>, + mat: Option, + ) -> Vec> { + pairs + .into_iter() + .map(|(p1, p2)| { + LineSegment3D::new() + .p1(p1.into()) + .p2(p2.into()) + .maybe_material(mat) + .build() + }) + .collect() } pub fn p1(&self) -> Point3D { @@ -84,12 +75,11 @@ where where U: Copy + Clone + std::fmt::Debug, { - 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 - } + LineSegment3D::new() + .p1(self.p1.cast_unit()) + .p2(self.p2.cast_unit()) + .maybe_material(self.material) + .build() } pub fn xy(self) -> LineSegment2D { @@ -114,7 +104,15 @@ where 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::new(p1, p2, self.material))) + p1t.and_then(|p1| { + p2t.map(|p2| { + LineSegment3D::new() + .p1(p1) + .p2(p2) + .maybe_material(self.material) + .build() + }) + }) } pub fn transform_without_metadata( @@ -127,7 +125,7 @@ where 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::new_segment(p1, p2))) + p1t.and_then(|p1| p2t.map(|p2| LineSegment3D::new().p1(p1).p2(p2).build())) } } @@ -166,7 +164,7 @@ where 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::new_segment(start, end) + LineSegment3D::new().p1(start).p2(end).build() } pub fn subsegments(&self) -> impl Iterator> + '_ { @@ -234,7 +232,11 @@ where .map(|ndx_group| { let start = self.get_subsegment(*ndx_group.start()).p1; let end = self.get_subsegment(*ndx_group.end()).p2; - LineSegment3D::new(start, end, self.parent.material) + LineSegment3D::new() + .p1(start) + .p2(end) + .maybe_material(self.parent.material) + .build() }) .collect() } diff --git a/raydeon/src/scene.rs b/raydeon/src/scene.rs index 418727a..7b37a02 100644 --- a/raydeon/src/scene.rs +++ b/raydeon/src/scene.rs @@ -298,7 +298,12 @@ impl<'a> SceneCamera<'a> { ) -> Vec> { let segments = segments .iter() - .map(|segment| LineSegment3D::new_segment(segment.p1.to_3d(), segment.p2.to_3d())) + .map(|segment| { + LineSegment3D::new() + .p1(segment.p1.to_3d()) + .p2(segment.p2.to_3d()) + .build() + }) .collect::>(); let mut split_segments = segments .iter() diff --git a/raydeon/src/shapes/aacuboid.rs b/raydeon/src/shapes/aacuboid.rs index 5993546..5d74f56 100644 --- a/raydeon/src/shapes/aacuboid.rs +++ b/raydeon/src/shapes/aacuboid.rs @@ -56,20 +56,23 @@ impl Shape for AxisAlignedCuboid { let p7 = WPoint3::new(x2, y2, z2); let p8 = WPoint3::new(x1, y2, z2); - vec![ - 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), - ] + LineSegment3D::from_points( + vec![ + (p1, p2), + (p2, p3), + (p3, p4), + (p4, p1), + (p5, p6), + (p6, p7), + (p7, p8), + (p8, p5), + (p1, p5), + (p2, p6), + (p3, p7), + (p4, p8), + ], + self.material, + ) } } diff --git a/raydeon/src/shapes/quad.rs b/raydeon/src/shapes/quad.rs index 40e31cc..9514aef 100644 --- a/raydeon/src/shapes/quad.rs +++ b/raydeon/src/shapes/quad.rs @@ -67,11 +67,9 @@ impl Shape for Quad { .map(|v| v + (v.to_vector() - centroid).normalize() * 0.0015) .collect::>(); - vec![ - 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), - ] + LineSegment3D::from_points( + vec![(v[0], v[1]), (v[1], v[2]), (v[2], v[3]), (v[3], v[0])], + self.material, + ) } } diff --git a/raydeon/src/shapes/triangle.rs b/raydeon/src/shapes/triangle.rs index 0acb759..544c7bd 100644 --- a/raydeon/src/shapes/triangle.rs +++ b/raydeon/src/shapes/triangle.rs @@ -52,11 +52,7 @@ impl Shape for Triangle { let v1 = v1 + (v1 - centroid).normalize() * 0.015; let v2 = v2 + (v2 - centroid).normalize() * 0.015; - vec![ - LineSegment3D::new(v0, v1, self.material), - LineSegment3D::new(v1, v2, self.material), - LineSegment3D::new(v2, v0, self.material), - ] + LineSegment3D::from_points(vec![(v0, v1), (v1, v2), (v2, v0)], self.material) } }