Skip to content

Commit

Permalink
feat: allow arbitrary metadata to be associated with shapes
Browse files Browse the repository at this point in the history
This allows users to associate materials or other metadata that can be
used during post-processing of the rendered image.

This also enables raydeon to support its own material types that can
later be used to implement its own lighting model.
  • Loading branch information
cbgbt committed Nov 24, 2024
1 parent 664fcb2 commit cf7f663
Show file tree
Hide file tree
Showing 12 changed files with 347 additions and 243 deletions.
3 changes: 3 additions & 0 deletions pyraydeon/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
use pyo3::prelude::*;

#[derive(Copy, Clone, Debug, Default)]
struct Material;

macro_rules! pywrap {
($name:ident, $wraps:ty) => {
#[derive(Debug, Clone, Copy)]
Expand Down
13 changes: 7 additions & 6 deletions pyraydeon/src/scene.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use raydeon::WorldSpace;

use crate::linear::{ArbitrarySpace, Point2, Point3, Vec3};
use crate::shapes::Geometry;
use crate::Material;

pywrap!(Camera, raydeon::Camera<raydeon::Perspective, raydeon::Observation>);

Expand Down Expand Up @@ -43,14 +44,14 @@ impl Camera {

#[pyclass(frozen)]
pub(crate) struct Scene {
scene: Arc<raydeon::Scene>,
scene: Arc<raydeon::Scene<Material>>,
}

#[pymethods]
impl Scene {
#[new]
fn new(py: Python, geometry: Vec<PyObject>) -> PyResult<Self> {
let geometry: Vec<Arc<dyn raydeon::Shape<WorldSpace>>> = geometry
let geometry: Vec<Arc<dyn raydeon::Shape<WorldSpace, Material>>> = geometry
.into_iter()
.map(|g| {
let geom: Py<Geometry> = g.extract(py)?;
Expand Down Expand Up @@ -106,25 +107,25 @@ impl LineSegment2D {
}
}

pywrap!(LineSegment3D, raydeon::path::LineSegment3D<ArbitrarySpace>);
pywrap!(LineSegment3D, raydeon::path::LineSegment3D<ArbitrarySpace, Material>);

#[pymethods]
impl LineSegment3D {
#[new]
fn new(p1: &Bound<'_, PyAny>, p2: &Bound<'_, PyAny>) -> PyResult<Self> {
let p1 = Point3::try_from(p1)?;
let p2 = Point3::try_from(p2)?;
Ok(raydeon::path::LineSegment3D::new(p1.cast_unit(), p2.cast_unit()).into())
Ok(raydeon::path::LineSegment3D::tagged(p1.cast_unit(), p2.cast_unit(), Material).into())
}

#[getter]
fn p1<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray<f64, Ix1>> {
PyArray::from_slice_bound(py, &self.0.p1.to_array())
PyArray::from_slice_bound(py, &self.0.p1().to_array())
}

#[getter]
fn p2<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray<f64, Ix1>> {
PyArray::from_slice_bound(py, &self.0.p2.to_array())
PyArray::from_slice_bound(py, &self.0.p2().to_array())
}

fn __repr__(slf: &Bound<'_, Self>) -> PyResult<String> {
Expand Down
11 changes: 6 additions & 5 deletions pyraydeon/src/shapes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,11 @@ pub(crate) use primitive::{AxisAlignedCuboid, Tri};

use crate::ray::{HitData, Ray, AABB3};
use crate::scene::{Camera, LineSegment3D};
use crate::Material;

#[derive(Debug)]
enum InnerGeometry {
Native(Arc<dyn raydeon::Shape<WorldSpace>>),
Native(Arc<dyn raydeon::Shape<WorldSpace, Material>>),
Py,
}

Expand All @@ -24,7 +25,7 @@ pub(crate) struct Geometry {
}

impl Geometry {
pub(crate) fn native(geom: Arc<dyn raydeon::Shape<WorldSpace>>) -> Self {
pub(crate) fn native(geom: Arc<dyn raydeon::Shape<WorldSpace, Material>>) -> Self {
let geom = InnerGeometry::Native(geom);
Self { geom }
}
Expand All @@ -34,7 +35,7 @@ impl Geometry {
Self { geom }
}

pub(crate) fn geometry(&self, obj: PyObject) -> Arc<dyn raydeon::Shape<WorldSpace>> {
pub(crate) fn geometry(&self, obj: PyObject) -> Arc<dyn raydeon::Shape<WorldSpace, Material>> {
match &self.geom {
InnerGeometry::Native(ref geom) => Arc::clone(geom),
InnerGeometry::Py => Arc::new(PythonGeometry::new(obj, PythonGeometryKind::Draw)),
Expand Down Expand Up @@ -173,7 +174,7 @@ impl PythonGeometry {
}
}

impl raydeon::Shape<WorldSpace> for PythonGeometry {
impl raydeon::Shape<WorldSpace, Material> for PythonGeometry {
fn collision_geometry(&self) -> Option<Vec<Arc<dyn raydeon::CollisionGeometry<WorldSpace>>>> {
let collision_geometry: Option<_> = Python::with_gil(|py| {
let inner = self.slf.bind(py);
Expand All @@ -200,7 +201,7 @@ impl raydeon::Shape<WorldSpace> for PythonGeometry {
fn paths(
&self,
cam: &raydeon::Camera<raydeon::Perspective, raydeon::Observation>,
) -> Vec<raydeon::path::LineSegment3D<WorldSpace>> {
) -> Vec<raydeon::path::LineSegment3D<WorldSpace, Material>> {
let segments: Option<_> = Python::with_gil(|py| {
let inner = self.slf.bind(py);
let cam = Camera::from(*cam);
Expand Down
54 changes: 26 additions & 28 deletions pyraydeon/src/shapes/primitive.rs
Original file line number Diff line number Diff line change
@@ -1,77 +1,74 @@
use super::{CollisionGeometry, Geometry};
use crate::linear::{Point3, Vec3};
use crate::Material;
use numpy::{PyArrayLike1, PyArrayLike2};
use pyo3::exceptions::PyIndexError;
use pyo3::prelude::*;
use raydeon::WorldSpace;
use std::sync::Arc;

#[pyclass(frozen, extends=Geometry, subclass)]
pub(crate) struct AxisAlignedCuboid(pub(crate) Arc<raydeon::shapes::AxisAlignedCuboid>);
pub(crate) struct AxisAlignedCuboid(pub(crate) Arc<raydeon::shapes::AxisAlignedCuboid<Material>>);

impl ::std::ops::Deref for AxisAlignedCuboid {
type Target = Arc<raydeon::shapes::AxisAlignedCuboid>;
type Target = Arc<raydeon::shapes::AxisAlignedCuboid<Material>>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl From<Arc<raydeon::shapes::AxisAlignedCuboid>> for AxisAlignedCuboid {
fn from(value: Arc<raydeon::shapes::AxisAlignedCuboid>) -> Self {
impl From<Arc<raydeon::shapes::AxisAlignedCuboid<Material>>> for AxisAlignedCuboid {
fn from(value: Arc<raydeon::shapes::AxisAlignedCuboid<Material>>) -> Self {
Self(value)
}
}

#[pymethods]
impl AxisAlignedCuboid {
#[new]
#[pyo3(signature = (min, max, tag=0))]
fn new(
min: &Bound<'_, PyAny>,
max: &Bound<'_, PyAny>,
tag: usize,
) -> PyResult<(Self, Geometry)> {
#[pyo3(signature = (min, max))]
fn new(min: &Bound<'_, PyAny>, max: &Bound<'_, PyAny>) -> PyResult<(Self, Geometry)> {
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(),
tag,
Material,
));
let geom = Geometry::native(Arc::clone(&shape) as Arc<dyn raydeon::Shape<WorldSpace>>);
let geom =
Geometry::native(Arc::clone(&shape) as Arc<dyn raydeon::Shape<WorldSpace, Material>>);

Ok((Self(shape), geom))
}
}

#[pyclass(frozen, extends=Geometry, subclass)]
pub(crate) struct Tri(pub(crate) Arc<raydeon::shapes::Triangle>);
pub(crate) struct Tri(pub(crate) Arc<raydeon::shapes::Triangle<Material>>);

impl ::std::ops::Deref for Tri {
type Target = Arc<raydeon::shapes::Triangle>;
type Target = Arc<raydeon::shapes::Triangle<Material>>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl From<Arc<raydeon::shapes::Triangle>> for Tri {
fn from(value: Arc<raydeon::shapes::Triangle>) -> Self {
impl From<Arc<raydeon::shapes::Triangle<Material>>> for Tri {
fn from(value: Arc<raydeon::shapes::Triangle<Material>>) -> Self {
Self(value)
}
}

#[pymethods]
impl Tri {
#[new]
#[pyo3(signature = (p1, p2, p3, tag=0))]
#[pyo3(signature = (p1, p2, p3))]
fn new(
p1: &Bound<'_, PyAny>,
p2: &Bound<'_, PyAny>,
p3: &Bound<'_, PyAny>,
tag: usize,
) -> PyResult<(Self, Geometry)> {
let p1: Point3 = p1.try_into()?;
let p2: Point3 = p2.try_into()?;
Expand All @@ -81,9 +78,10 @@ impl Tri {
p1.cast_unit(),
p2.cast_unit(),
p3.cast_unit(),
tag,
Material,
));
let geom = Geometry::native(Arc::clone(&shape) as Arc<dyn raydeon::Shape<WorldSpace>>);
let geom =
Geometry::native(Arc::clone(&shape) as Arc<dyn raydeon::Shape<WorldSpace, Material>>);
Ok((Self(shape), geom))
}
}
Expand Down Expand Up @@ -127,31 +125,30 @@ impl Plane {
}

#[pyclass(frozen, extends=Geometry, subclass)]
pub(crate) struct Quad(pub(crate) Arc<raydeon::shapes::Quad>);
pub(crate) struct Quad(pub(crate) Arc<raydeon::shapes::Quad<Material>>);

impl ::std::ops::Deref for Quad {
type Target = Arc<raydeon::shapes::Quad>;
type Target = Arc<raydeon::shapes::Quad<Material>>;

fn deref(&self) -> &Self::Target {
&self.0
}
}

impl From<Arc<raydeon::shapes::Quad>> for Quad {
fn from(value: Arc<raydeon::shapes::Quad>) -> Self {
impl From<Arc<raydeon::shapes::Quad<Material>>> for Quad {
fn from(value: Arc<raydeon::shapes::Quad<Material>>) -> Self {
Self(value)
}
}

#[pymethods]
impl Quad {
#[new]
#[pyo3(signature = (origin, basis, dims, tag=0))]
#[pyo3(signature = (origin, basis, dims))]
fn new(
origin: &Bound<'_, PyAny>,
basis: PyArrayLike2<'_, f64>,
dims: PyArrayLike1<'_, f64>,
tag: usize,
) -> PyResult<(Self, Geometry)> {
let origin: Point3 = origin.try_into()?;
let basis = basis
Expand Down Expand Up @@ -188,9 +185,10 @@ impl Quad {
origin.0.cast_unit(),
basis,
dims,
tag,
Material,
));
let geom = Geometry::native(Arc::clone(&shape) as Arc<dyn raydeon::Shape<WorldSpace>>);
let geom =
Geometry::native(Arc::clone(&shape) as Arc<dyn raydeon::Shape<WorldSpace, Material>>);
Ok((Self(shape), geom))
}
}
4 changes: 2 additions & 2 deletions raydeon/examples/geom_perf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ const LENGTH: usize = 100;
const CELL_WIDTH: f64 = 2.0;
const CELL_LENGTH: f64 = 3.0;

fn generate_scene() -> Vec<Arc<dyn Shape<WorldSpace>>> {
let mut scene: Vec<Arc<dyn Shape<WorldSpace>>> = Vec::new();
fn generate_scene() -> Vec<Arc<dyn Shape<WorldSpace, usize>>> {
let mut scene: Vec<Arc<dyn Shape<_, _>>> = Vec::new();

for i in 0..WIDTH {
for j in 0..LENGTH {
Expand Down
38 changes: 11 additions & 27 deletions raydeon/src/camera.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use euclid::Transform3D;
use path::SlicedSegment3D;

use crate::*;

Expand Down Expand Up @@ -165,12 +166,12 @@ impl Camera<Perspective, Observation> {
}

/// Chops a line segment into subsegments based on distance from camera
pub fn chop_segment(
pub fn chop_segment<'a, P: PathMeta>(
&self,
segment: &LineSegment3D<WorldSpace>,
) -> Vec<LineSegment3D<WorldSpace>> {
let p1 = segment.p1.to_vector();
let p2 = segment.p2.to_vector();
segment: &'a LineSegment3D<WorldSpace, P>,
) -> Option<SlicedSegment3D<'a, WorldSpace, P>> {
let p1 = segment.p1().to_vector();
let p2 = segment.p2().to_vector();

// Transform the points to camera space, then chop based on the pixel length
let transformation = self.camera_transformation();
Expand All @@ -189,35 +190,18 @@ impl Camera<Perspective, Observation> {
let chunk_count = canvas_points
.map(|(p1t, p2t)| {
let rough_chop_size = (p2t - p1t).length() / (pen_px_size as f64 / 2.0);
rough_chop_size.round_ties_even() as u32
rough_chop_size.round_ties_even() as usize
})
.unwrap_or_else(|| {
let rough_chop_size = self.min_step_size();
((p2 - p1).length() / rough_chop_size).round_ties_even() as u32
((p2 - p1).length() / rough_chop_size).round_ties_even() as usize
});

if chunk_count == 0 {
return vec![];
None
} else {
Some(SlicedSegment3D::new(chunk_count, segment))
}
if chunk_count == 1 {
return vec![*segment];
}

let segment_diff = p2 - p1;
let segment_length = segment_diff.length();

let true_chunk_len = segment_length / chunk_count as f64;
let true_chunk_len = f64::min(true_chunk_len, segment_length);

let segment_dir = segment_diff.normalize();
let chunk_vec = segment_dir * true_chunk_len;
(0..chunk_count)
.map(|segment_ndx| {
let p1 = segment.p1 + (chunk_vec * (segment_ndx as f64));
let p2 = p1 + chunk_vec;
LineSegment3D::tagged(p1, p2, segment.tag)
})
.collect()
}
}

Expand Down
7 changes: 4 additions & 3 deletions raydeon/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub mod shapes;

use std::sync::Arc;

use path::LineSegment3D;
pub use path::{LineSegment3D, PathMeta};
pub use ray::{HitData, Ray};

pub use camera::{Camera, NoObservation, NoPerspective, Observation, Perspective};
Expand Down Expand Up @@ -44,12 +44,13 @@ pub type WCTransform = Transform3<WorldSpace, CameraSpace>;
pub type WWTransform = Transform3<WorldSpace, WorldSpace>;
pub type CCTransform = Transform3<CameraSpace, CameraSpace>;

pub trait Shape<Space>: Send + Sync + std::fmt::Debug
pub trait Shape<Space, Meta>: Send + Sync + std::fmt::Debug
where
Space: Sized + Send + Sync + std::fmt::Debug + Copy + Clone,
Meta: PathMeta,
{
fn collision_geometry(&self) -> Option<Vec<Arc<dyn CollisionGeometry<Space>>>>;
fn paths(&self, cam: &Camera<Perspective, Observation>) -> Vec<LineSegment3D<Space>>;
fn paths(&self, cam: &Camera<Perspective, Observation>) -> Vec<LineSegment3D<Space, Meta>>;
}

pub trait CollisionGeometry<Space>: Send + Sync + std::fmt::Debug
Expand Down
Loading

0 comments on commit cf7f663

Please sign in to comment.