Skip to content

Commit cf7f663

Browse files
committed
feat: allow arbitrary metadata to be associated with shapes
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.
1 parent 664fcb2 commit cf7f663

File tree

12 files changed

+347
-243
lines changed

12 files changed

+347
-243
lines changed

pyraydeon/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
use pyo3::prelude::*;
22

3+
#[derive(Copy, Clone, Debug, Default)]
4+
struct Material;
5+
36
macro_rules! pywrap {
47
($name:ident, $wraps:ty) => {
58
#[derive(Debug, Clone, Copy)]

pyraydeon/src/scene.rs

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use raydeon::WorldSpace;
66

77
use crate::linear::{ArbitrarySpace, Point2, Point3, Vec3};
88
use crate::shapes::Geometry;
9+
use crate::Material;
910

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

@@ -43,14 +44,14 @@ impl Camera {
4344

4445
#[pyclass(frozen)]
4546
pub(crate) struct Scene {
46-
scene: Arc<raydeon::Scene>,
47+
scene: Arc<raydeon::Scene<Material>>,
4748
}
4849

4950
#[pymethods]
5051
impl Scene {
5152
#[new]
5253
fn new(py: Python, geometry: Vec<PyObject>) -> PyResult<Self> {
53-
let geometry: Vec<Arc<dyn raydeon::Shape<WorldSpace>>> = geometry
54+
let geometry: Vec<Arc<dyn raydeon::Shape<WorldSpace, Material>>> = geometry
5455
.into_iter()
5556
.map(|g| {
5657
let geom: Py<Geometry> = g.extract(py)?;
@@ -106,25 +107,25 @@ impl LineSegment2D {
106107
}
107108
}
108109

109-
pywrap!(LineSegment3D, raydeon::path::LineSegment3D<ArbitrarySpace>);
110+
pywrap!(LineSegment3D, raydeon::path::LineSegment3D<ArbitrarySpace, Material>);
110111

111112
#[pymethods]
112113
impl LineSegment3D {
113114
#[new]
114115
fn new(p1: &Bound<'_, PyAny>, p2: &Bound<'_, PyAny>) -> PyResult<Self> {
115116
let p1 = Point3::try_from(p1)?;
116117
let p2 = Point3::try_from(p2)?;
117-
Ok(raydeon::path::LineSegment3D::new(p1.cast_unit(), p2.cast_unit()).into())
118+
Ok(raydeon::path::LineSegment3D::tagged(p1.cast_unit(), p2.cast_unit(), Material).into())
118119
}
119120

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

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

130131
fn __repr__(slf: &Bound<'_, Self>) -> PyResult<String> {

pyraydeon/src/shapes/mod.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,11 @@ pub(crate) use primitive::{AxisAlignedCuboid, Tri};
1010

1111
use crate::ray::{HitData, Ray, AABB3};
1212
use crate::scene::{Camera, LineSegment3D};
13+
use crate::Material;
1314

1415
#[derive(Debug)]
1516
enum InnerGeometry {
16-
Native(Arc<dyn raydeon::Shape<WorldSpace>>),
17+
Native(Arc<dyn raydeon::Shape<WorldSpace, Material>>),
1718
Py,
1819
}
1920

@@ -24,7 +25,7 @@ pub(crate) struct Geometry {
2425
}
2526

2627
impl Geometry {
27-
pub(crate) fn native(geom: Arc<dyn raydeon::Shape<WorldSpace>>) -> Self {
28+
pub(crate) fn native(geom: Arc<dyn raydeon::Shape<WorldSpace, Material>>) -> Self {
2829
let geom = InnerGeometry::Native(geom);
2930
Self { geom }
3031
}
@@ -34,7 +35,7 @@ impl Geometry {
3435
Self { geom }
3536
}
3637

37-
pub(crate) fn geometry(&self, obj: PyObject) -> Arc<dyn raydeon::Shape<WorldSpace>> {
38+
pub(crate) fn geometry(&self, obj: PyObject) -> Arc<dyn raydeon::Shape<WorldSpace, Material>> {
3839
match &self.geom {
3940
InnerGeometry::Native(ref geom) => Arc::clone(geom),
4041
InnerGeometry::Py => Arc::new(PythonGeometry::new(obj, PythonGeometryKind::Draw)),
@@ -173,7 +174,7 @@ impl PythonGeometry {
173174
}
174175
}
175176

176-
impl raydeon::Shape<WorldSpace> for PythonGeometry {
177+
impl raydeon::Shape<WorldSpace, Material> for PythonGeometry {
177178
fn collision_geometry(&self) -> Option<Vec<Arc<dyn raydeon::CollisionGeometry<WorldSpace>>>> {
178179
let collision_geometry: Option<_> = Python::with_gil(|py| {
179180
let inner = self.slf.bind(py);
@@ -200,7 +201,7 @@ impl raydeon::Shape<WorldSpace> for PythonGeometry {
200201
fn paths(
201202
&self,
202203
cam: &raydeon::Camera<raydeon::Perspective, raydeon::Observation>,
203-
) -> Vec<raydeon::path::LineSegment3D<WorldSpace>> {
204+
) -> Vec<raydeon::path::LineSegment3D<WorldSpace, Material>> {
204205
let segments: Option<_> = Python::with_gil(|py| {
205206
let inner = self.slf.bind(py);
206207
let cam = Camera::from(*cam);

pyraydeon/src/shapes/primitive.rs

Lines changed: 26 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,74 @@
11
use super::{CollisionGeometry, Geometry};
22
use crate::linear::{Point3, Vec3};
3+
use crate::Material;
34
use numpy::{PyArrayLike1, PyArrayLike2};
45
use pyo3::exceptions::PyIndexError;
56
use pyo3::prelude::*;
67
use raydeon::WorldSpace;
78
use std::sync::Arc;
89

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

1213
impl ::std::ops::Deref for AxisAlignedCuboid {
13-
type Target = Arc<raydeon::shapes::AxisAlignedCuboid>;
14+
type Target = Arc<raydeon::shapes::AxisAlignedCuboid<Material>>;
1415

1516
fn deref(&self) -> &Self::Target {
1617
&self.0
1718
}
1819
}
1920

20-
impl From<Arc<raydeon::shapes::AxisAlignedCuboid>> for AxisAlignedCuboid {
21-
fn from(value: Arc<raydeon::shapes::AxisAlignedCuboid>) -> Self {
21+
impl From<Arc<raydeon::shapes::AxisAlignedCuboid<Material>>> for AxisAlignedCuboid {
22+
fn from(value: Arc<raydeon::shapes::AxisAlignedCuboid<Material>>) -> Self {
2223
Self(value)
2324
}
2425
}
2526

2627
#[pymethods]
2728
impl AxisAlignedCuboid {
2829
#[new]
29-
#[pyo3(signature = (min, max, tag=0))]
30-
fn new(
31-
min: &Bound<'_, PyAny>,
32-
max: &Bound<'_, PyAny>,
33-
tag: usize,
34-
) -> PyResult<(Self, Geometry)> {
30+
#[pyo3(signature = (min, max))]
31+
fn new(min: &Bound<'_, PyAny>, max: &Bound<'_, PyAny>) -> PyResult<(Self, Geometry)> {
3532
let min: Vec3 = min.try_into()?;
3633
let max: Vec3 = max.try_into()?;
3734

3835
let shape = Arc::new(raydeon::shapes::AxisAlignedCuboid::tagged(
3936
min.cast_unit(),
4037
max.cast_unit(),
41-
tag,
38+
Material,
4239
));
43-
let geom = Geometry::native(Arc::clone(&shape) as Arc<dyn raydeon::Shape<WorldSpace>>);
40+
let geom =
41+
Geometry::native(Arc::clone(&shape) as Arc<dyn raydeon::Shape<WorldSpace, Material>>);
4442

4543
Ok((Self(shape), geom))
4644
}
4745
}
4846

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

5250
impl ::std::ops::Deref for Tri {
53-
type Target = Arc<raydeon::shapes::Triangle>;
51+
type Target = Arc<raydeon::shapes::Triangle<Material>>;
5452

5553
fn deref(&self) -> &Self::Target {
5654
&self.0
5755
}
5856
}
5957

60-
impl From<Arc<raydeon::shapes::Triangle>> for Tri {
61-
fn from(value: Arc<raydeon::shapes::Triangle>) -> Self {
58+
impl From<Arc<raydeon::shapes::Triangle<Material>>> for Tri {
59+
fn from(value: Arc<raydeon::shapes::Triangle<Material>>) -> Self {
6260
Self(value)
6361
}
6462
}
6563

6664
#[pymethods]
6765
impl Tri {
6866
#[new]
69-
#[pyo3(signature = (p1, p2, p3, tag=0))]
67+
#[pyo3(signature = (p1, p2, p3))]
7068
fn new(
7169
p1: &Bound<'_, PyAny>,
7270
p2: &Bound<'_, PyAny>,
7371
p3: &Bound<'_, PyAny>,
74-
tag: usize,
7572
) -> PyResult<(Self, Geometry)> {
7673
let p1: Point3 = p1.try_into()?;
7774
let p2: Point3 = p2.try_into()?;
@@ -81,9 +78,10 @@ impl Tri {
8178
p1.cast_unit(),
8279
p2.cast_unit(),
8380
p3.cast_unit(),
84-
tag,
81+
Material,
8582
));
86-
let geom = Geometry::native(Arc::clone(&shape) as Arc<dyn raydeon::Shape<WorldSpace>>);
83+
let geom =
84+
Geometry::native(Arc::clone(&shape) as Arc<dyn raydeon::Shape<WorldSpace, Material>>);
8785
Ok((Self(shape), geom))
8886
}
8987
}
@@ -127,31 +125,30 @@ impl Plane {
127125
}
128126

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

132130
impl ::std::ops::Deref for Quad {
133-
type Target = Arc<raydeon::shapes::Quad>;
131+
type Target = Arc<raydeon::shapes::Quad<Material>>;
134132

135133
fn deref(&self) -> &Self::Target {
136134
&self.0
137135
}
138136
}
139137

140-
impl From<Arc<raydeon::shapes::Quad>> for Quad {
141-
fn from(value: Arc<raydeon::shapes::Quad>) -> Self {
138+
impl From<Arc<raydeon::shapes::Quad<Material>>> for Quad {
139+
fn from(value: Arc<raydeon::shapes::Quad<Material>>) -> Self {
142140
Self(value)
143141
}
144142
}
145143

146144
#[pymethods]
147145
impl Quad {
148146
#[new]
149-
#[pyo3(signature = (origin, basis, dims, tag=0))]
147+
#[pyo3(signature = (origin, basis, dims))]
150148
fn new(
151149
origin: &Bound<'_, PyAny>,
152150
basis: PyArrayLike2<'_, f64>,
153151
dims: PyArrayLike1<'_, f64>,
154-
tag: usize,
155152
) -> PyResult<(Self, Geometry)> {
156153
let origin: Point3 = origin.try_into()?;
157154
let basis = basis
@@ -188,9 +185,10 @@ impl Quad {
188185
origin.0.cast_unit(),
189186
basis,
190187
dims,
191-
tag,
188+
Material,
192189
));
193-
let geom = Geometry::native(Arc::clone(&shape) as Arc<dyn raydeon::Shape<WorldSpace>>);
190+
let geom =
191+
Geometry::native(Arc::clone(&shape) as Arc<dyn raydeon::Shape<WorldSpace, Material>>);
194192
Ok((Self(shape), geom))
195193
}
196194
}

raydeon/examples/geom_perf.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ const LENGTH: usize = 100;
7272
const CELL_WIDTH: f64 = 2.0;
7373
const CELL_LENGTH: f64 = 3.0;
7474

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

7878
for i in 0..WIDTH {
7979
for j in 0..LENGTH {

raydeon/src/camera.rs

Lines changed: 11 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
use euclid::Transform3D;
2+
use path::SlicedSegment3D;
23

34
use crate::*;
45

@@ -165,12 +166,12 @@ impl Camera<Perspective, Observation> {
165166
}
166167

167168
/// Chops a line segment into subsegments based on distance from camera
168-
pub fn chop_segment(
169+
pub fn chop_segment<'a, P: PathMeta>(
169170
&self,
170-
segment: &LineSegment3D<WorldSpace>,
171-
) -> Vec<LineSegment3D<WorldSpace>> {
172-
let p1 = segment.p1.to_vector();
173-
let p2 = segment.p2.to_vector();
171+
segment: &'a LineSegment3D<WorldSpace, P>,
172+
) -> Option<SlicedSegment3D<'a, WorldSpace, P>> {
173+
let p1 = segment.p1().to_vector();
174+
let p2 = segment.p2().to_vector();
174175

175176
// Transform the points to camera space, then chop based on the pixel length
176177
let transformation = self.camera_transformation();
@@ -189,35 +190,18 @@ impl Camera<Perspective, Observation> {
189190
let chunk_count = canvas_points
190191
.map(|(p1t, p2t)| {
191192
let rough_chop_size = (p2t - p1t).length() / (pen_px_size as f64 / 2.0);
192-
rough_chop_size.round_ties_even() as u32
193+
rough_chop_size.round_ties_even() as usize
193194
})
194195
.unwrap_or_else(|| {
195196
let rough_chop_size = self.min_step_size();
196-
((p2 - p1).length() / rough_chop_size).round_ties_even() as u32
197+
((p2 - p1).length() / rough_chop_size).round_ties_even() as usize
197198
});
198199

199200
if chunk_count == 0 {
200-
return vec![];
201+
None
202+
} else {
203+
Some(SlicedSegment3D::new(chunk_count, segment))
201204
}
202-
if chunk_count == 1 {
203-
return vec![*segment];
204-
}
205-
206-
let segment_diff = p2 - p1;
207-
let segment_length = segment_diff.length();
208-
209-
let true_chunk_len = segment_length / chunk_count as f64;
210-
let true_chunk_len = f64::min(true_chunk_len, segment_length);
211-
212-
let segment_dir = segment_diff.normalize();
213-
let chunk_vec = segment_dir * true_chunk_len;
214-
(0..chunk_count)
215-
.map(|segment_ndx| {
216-
let p1 = segment.p1 + (chunk_vec * (segment_ndx as f64));
217-
let p2 = p1 + chunk_vec;
218-
LineSegment3D::tagged(p1, p2, segment.tag)
219-
})
220-
.collect()
221205
}
222206
}
223207

raydeon/src/lib.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ pub mod shapes;
88

99
use std::sync::Arc;
1010

11-
use path::LineSegment3D;
11+
pub use path::{LineSegment3D, PathMeta};
1212
pub use ray::{HitData, Ray};
1313

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

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

5556
pub trait CollisionGeometry<Space>: Send + Sync + std::fmt::Debug

0 commit comments

Comments
 (0)