Skip to content

Commit 2b9cfdd

Browse files
committed
feat: allow specifying materials per-drawable
1 parent e881806 commit 2b9cfdd

File tree

23 files changed

+1562
-1201
lines changed

23 files changed

+1562
-1201
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ anyhow = "1"
1313
bon = "3"
1414
cgmath = "0.17"
1515
collision = "0.20"
16+
dot_vox = "5"
1617
env_logger = "0.11"
1718
euclid = "0.22"
1819
float-cmp = "0.5"

README.md

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -24,38 +24,45 @@ lighting.
2424
```rust
2525
use raydeon::lights::PointLight;
2626
use raydeon::shapes::AxisAlignedCuboid;
27-
use raydeon::Material;
2827
use raydeon::{Camera, Scene, SceneLighting, WPoint3, WVec3};
28+
use raydeon::{DrawableShape, Material};
2929
use std::sync::Arc;
3030

3131
fn main() {
3232
env_logger::Builder::from_default_env()
3333
.format_timestamp_nanos()
3434
.init();
3535

36+
let cube_material = Material::new_mat(3.0, 2.0, 2.0, 0);
3637
let scene = Scene::new()
3738
.geometry(vec![
38-
Arc::new(
39-
AxisAlignedCuboid::new()
40-
.min((-1.0, -1.0, -1.0))
41-
.max((1.0, 1.0, 1.0))
42-
.material(Material::new(3.0, 2.0, 2.0, 0))
43-
.build(),
44-
),
45-
Arc::new(
46-
AxisAlignedCuboid::new()
47-
.min((1.8, -1.0, -1.0))
48-
.max((3.8, 1.0, 1.0))
49-
.material(Material::new(2.0, 2.0, 2.0, 0))
50-
.build(),
51-
),
52-
Arc::new(
53-
AxisAlignedCuboid::new()
54-
.min((-1.4, 1.8, -1.0))
55-
.max((0.6, 3.8, 1.0))
56-
.material(Material::new(3.0, 2.0, 2.0, 0))
57-
.build(),
58-
),
39+
DrawableShape::new()
40+
.geometry(Arc::new(
41+
AxisAlignedCuboid::new()
42+
.min((-1.0, -1.0, -1.0))
43+
.max((1.0, 1.0, 1.0))
44+
.build(),
45+
))
46+
.material(cube_material)
47+
.build(),
48+
DrawableShape::new()
49+
.geometry(Arc::new(
50+
AxisAlignedCuboid::new()
51+
.min((1.8, -1.0, -1.0))
52+
.max((3.8, 1.0, 1.0))
53+
.build(),
54+
))
55+
.material(cube_material)
56+
.build(),
57+
DrawableShape::new()
58+
.geometry(Arc::new(
59+
AxisAlignedCuboid::new()
60+
.min((-1.4, 1.8, -1.0))
61+
.max((0.6, 3.8, 1.0))
62+
.build(),
63+
))
64+
.material(cube_material)
65+
.build(),
5966
])
6067
.lighting(
6168
SceneLighting::new()
@@ -86,10 +93,7 @@ fn main() {
8693
.perspective(Camera::perspective(fovy, width, height, znear, zfar))
8794
.build();
8895

89-
let render_result = scene
90-
.attach_camera(camera)
91-
.with_seed(0)
92-
.render_with_lighting();
96+
let render_result = scene.attach_camera(camera).render_with_lighting();
9397

9498
let mut svg_doc = svg::Document::new()
9599
.set("width", "8in")
@@ -111,11 +115,7 @@ fn main() {
111115
let mut item_group = svg::node::element::Group::new()
112116
.set("transform", format!("translate(0, {}) scale(1,-1)", height));
113117

114-
for path in render_result
115-
.geometry_paths
116-
.iter()
117-
.chain(render_result.hatch_paths.iter())
118-
{
118+
for path in render_result {
119119
let (p1, p2) = (path.p1, path.p2);
120120
item_group = item_group.add(
121121
svg::node::element::Line::new()

pyraydeon/examples/py_sphere.py

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,9 @@
1818

1919

2020
class PySphere(Geometry):
21-
def __init__(self, point, radius, material=None):
22-
if material is not None:
23-
self._material = material
24-
21+
def __init__(self, point, radius):
2522
self.sphere = Sphere(point, radius)
2623

27-
@property
28-
def material(self):
29-
return self._material
30-
3124
def collision_geometry(self):
3225
return [self.sphere]
3326

@@ -71,16 +64,9 @@ def paths(self, cam):
7164

7265

7366
class PyPlane(Geometry):
74-
def __init__(self, point, normal, material=None):
75-
if material is not None:
76-
self._material = material
77-
67+
def __init__(self, point, normal):
7868
self.plane = Plane(point, normal)
7969

80-
@property
81-
def material(self):
82-
return self._material
83-
8470
def collision_geometry(self):
8571
return [self.plane]
8672

@@ -90,8 +76,14 @@ def paths(self, cam):
9076

9177
scene = Scene(
9278
geometry=[
93-
PySphere(Point3(0, 0, 0), 1.0, Material(3.0, 3.0, 3)),
94-
PyPlane(Point3(0, -2, 0), Vec3(0, 1, 0), Material(9000.0, 3.0, 3)),
79+
PySphere(
80+
Point3(0, 0, 0),
81+
1.0,
82+
).with_material(Material(3.0, 3.0, 3)),
83+
PyPlane(
84+
Point3(0, -2, 0),
85+
Vec3(0, 1, 0),
86+
).with_material(Material(9000.0, 3.0, 3)),
9587
],
9688
lights=[PointLight((4, 3, 10), 3.6, 2.0, 0.15, 0.4, 0.11)],
9789
ambient_light=0.13,

pyraydeon/src/drawables.rs

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
use std::sync::Arc;
2+
3+
use numpy::{Ix1, PyArray};
4+
use pyo3::prelude::*;
5+
6+
use crate::material::Material;
7+
use crate::shapes::Geometry;
8+
9+
#[derive(Debug)]
10+
#[pyclass(frozen)]
11+
/// A `DrawableShape` is the input geometry for a pyraydeon scene.
12+
///
13+
/// It is essentially some drawable geometry joined with a given material.
14+
pub(crate) struct DrawableShape {
15+
pub raydeon_drawable: raydeon::DrawableShape,
16+
pub pyobj: PyObject,
17+
}
18+
19+
impl ::std::ops::Deref for DrawableShape {
20+
type Target = raydeon::DrawableShape;
21+
22+
fn deref(&self) -> &Self::Target {
23+
&self.raydeon_drawable
24+
}
25+
}
26+
27+
impl DrawableShape {
28+
pub(crate) fn raydeon_drawable(&self) -> raydeon::DrawableShape {
29+
self.raydeon_drawable.clone()
30+
}
31+
}
32+
33+
impl<'py> FromPyObject<'py> for DrawableShape {
34+
fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult<Self> {
35+
let py = obj.py();
36+
let shape: PyObject = obj.getattr("shape")?.extract()?;
37+
let material: Option<Material> = obj.getattr("material")?.extract()?;
38+
39+
let raydeon_geometry = raydeon_geometry_from_py_object(py, &shape)?;
40+
let raydeon_drawable = raydeon::DrawableShape::new()
41+
.geometry(raydeon_geometry)
42+
.maybe_material(material.map(|m| m.0))
43+
.build();
44+
45+
Ok(DrawableShape {
46+
raydeon_drawable,
47+
pyobj: shape,
48+
})
49+
}
50+
}
51+
52+
#[pymethods]
53+
impl DrawableShape {
54+
#[new]
55+
#[pyo3(signature = (geometry, material=None))]
56+
fn new(py: Python, geometry: PyObject, material: Option<Material>) -> PyResult<Self> {
57+
let raydeon_geometry = raydeon_geometry_from_py_object(py, &geometry)?;
58+
let material = material.map(|m| m.0);
59+
let raydeon_drawable = raydeon::DrawableShape::new()
60+
.geometry(raydeon_geometry)
61+
.maybe_material(material)
62+
.build();
63+
64+
Ok(DrawableShape {
65+
raydeon_drawable,
66+
pyobj: geometry,
67+
})
68+
}
69+
70+
#[getter]
71+
fn material(&self) -> Option<Material> {
72+
self.raydeon_drawable.material().map(Into::into)
73+
}
74+
75+
#[getter]
76+
fn shape(&self, py: Python) -> PyObject {
77+
self.pyobj.clone_ref(py)
78+
}
79+
80+
fn collision_geometry(&self, py: Python) -> PyResult<PyObject> {
81+
self.pyobj.call_method0(py, "collision_geometry")
82+
}
83+
84+
fn paths(&self, py: Python) -> PyResult<PyObject> {
85+
self.pyobj.call_method0(py, "paths")
86+
}
87+
88+
fn __repr__(slf: &Bound<'_, Self>) -> PyResult<String> {
89+
let class_name = slf.get_type().qualname()?;
90+
Ok(format!("{}<{:#?}>", class_name, slf.borrow()))
91+
}
92+
}
93+
94+
pub(crate) fn raydeon_geometry_from_py_object(
95+
py: Python,
96+
g: &PyObject,
97+
) -> PyResult<Arc<dyn raydeon::Shape>> {
98+
let geom: Py<Geometry> = g.extract(py)?;
99+
let raydeon_shape = geom.borrow(py);
100+
let raydeon_shape = raydeon_shape.geometry(g.clone_ref(py));
101+
Ok(raydeon_shape)
102+
}
103+
104+
#[derive(Debug)]
105+
#[pyclass(frozen)]
106+
/// The output of a raydeon render.
107+
///
108+
/// A line segment, associated with a shape.
109+
pub(crate) struct DrawableSegment {
110+
p1: [f64; 2],
111+
p2: [f64; 2],
112+
kind: SegmentKind,
113+
114+
raydeon_drawable: Option<raydeon::DrawableShape>,
115+
}
116+
117+
impl From<raydeon::DrawableSegment<'_>> for DrawableSegment {
118+
fn from(value: raydeon::DrawableSegment<'_>) -> Self {
119+
let p1 = value.p1.to_array();
120+
let p2 = value.p2.to_array();
121+
122+
let raydeon_drawable = value.segment.get_shape().cloned();
123+
124+
let kind = value.kind.into();
125+
126+
Self {
127+
p1,
128+
p2,
129+
raydeon_drawable,
130+
kind,
131+
}
132+
}
133+
}
134+
135+
#[pymethods]
136+
impl DrawableSegment {
137+
#[getter]
138+
fn p1<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray<f64, Ix1>> {
139+
PyArray::from_slice_bound(py, &self.p1)
140+
}
141+
142+
#[getter]
143+
fn p2<'py>(&self, py: Python<'py>) -> Bound<'py, PyArray<f64, Ix1>> {
144+
PyArray::from_slice_bound(py, &self.p2)
145+
}
146+
147+
#[getter]
148+
fn material(&self) -> Option<Material> {
149+
self.raydeon_drawable
150+
.as_ref()
151+
.and_then(|d| d.material())
152+
.map(|m| m.into())
153+
}
154+
155+
#[getter]
156+
fn kind(&self) -> SegmentKind {
157+
self.kind
158+
}
159+
160+
fn __repr__(slf: &Bound<'_, Self>) -> PyResult<String> {
161+
let class_name = slf.get_type().qualname()?;
162+
Ok(format!("{}<{:#?}>", class_name, slf.borrow()))
163+
}
164+
}
165+
166+
#[pyclass(eq)]
167+
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
168+
pub(crate) enum SegmentKind {
169+
VerticalHatch,
170+
DiagonalHatch,
171+
Path,
172+
}
173+
174+
impl From<raydeon::SegmentKind> for SegmentKind {
175+
fn from(value: raydeon::SegmentKind) -> Self {
176+
match value {
177+
raydeon::SegmentKind::ScreenSpaceHatch(raydeon::ScreenSpaceHatchKind::Vertical) => {
178+
SegmentKind::VerticalHatch
179+
}
180+
raydeon::SegmentKind::ScreenSpaceHatch(raydeon::ScreenSpaceHatchKind::Diagonal60) => {
181+
SegmentKind::VerticalHatch
182+
}
183+
raydeon::SegmentKind::Path => SegmentKind::Path,
184+
}
185+
}
186+
}
187+
188+
pub(crate) fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
189+
m.add_class::<DrawableShape>()?;
190+
m.add_class::<DrawableSegment>()?;
191+
Ok(())
192+
}

pyraydeon/src/lib.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ macro_rules! pywrap {
2323
}
2424

2525
mod camera;
26+
mod drawables;
2627
mod light;
2728
mod linear;
2829
mod material;
@@ -44,5 +45,7 @@ fn pyraydeon(m: &Bound<'_, PyModule>) -> PyResult<()> {
4445
crate::material::register(m)?;
4546
crate::light::register(m)?;
4647

48+
crate::drawables::register(m)?;
49+
4750
Ok(())
4851
}

pyraydeon/src/material.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@ impl Material {
77
#[new]
88
#[pyo3(signature = (diffuse=0.0, specular=0.0, shininess=0.0, tag=0))]
99
fn new(diffuse: f64, specular: f64, shininess: f64, tag: usize) -> PyResult<Self> {
10-
Ok(raydeon::material::Material::new(diffuse, specular, shininess, tag).into())
10+
Ok(raydeon::material::Material::new()
11+
.diffuse(diffuse)
12+
.specular(specular)
13+
.shininess(shininess)
14+
.tag(tag)
15+
.build()
16+
.into())
1117
}
1218

1319
#[getter]

0 commit comments

Comments
 (0)