Skip to content

Commit

Permalink
feat: allow specifying materials per-drawable
Browse files Browse the repository at this point in the history
  • Loading branch information
cbgbt committed Nov 29, 2024
1 parent e881806 commit bb6402c
Show file tree
Hide file tree
Showing 23 changed files with 1,562 additions and 1,201 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ anyhow = "1"
bon = "3"
cgmath = "0.17"
collision = "0.20"
dot_vox = "5"
env_logger = "0.11"
euclid = "0.22"
float-cmp = "0.5"
Expand Down
62 changes: 31 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,38 +24,45 @@ lighting.
```rust
use raydeon::lights::PointLight;
use raydeon::shapes::AxisAlignedCuboid;
use raydeon::Material;
use raydeon::{Camera, Scene, SceneLighting, WPoint3, WVec3};
use raydeon::{DrawableShape, Material};
use std::sync::Arc;

fn main() {
env_logger::Builder::from_default_env()
.format_timestamp_nanos()
.init();

let cube_material = Material::new_mat(3.0, 2.0, 2.0, 0);
let scene = Scene::new()
.geometry(vec![
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(),
),
DrawableShape::new()
.geometry(Arc::new(
AxisAlignedCuboid::new()
.min((-1.0, -1.0, -1.0))
.max((1.0, 1.0, 1.0))
.build(),
))
.material(cube_material)
.build(),
DrawableShape::new()
.geometry(Arc::new(
AxisAlignedCuboid::new()
.min((1.8, -1.0, -1.0))
.max((3.8, 1.0, 1.0))
.build(),
))
.material(cube_material)
.build(),
DrawableShape::new()
.geometry(Arc::new(
AxisAlignedCuboid::new()
.min((-1.4, 1.8, -1.0))
.max((0.6, 3.8, 1.0))
.build(),
))
.material(cube_material)
.build(),
])
.lighting(
SceneLighting::new()
Expand Down Expand Up @@ -86,10 +93,7 @@ fn main() {
.perspective(Camera::perspective(fovy, width, height, znear, zfar))
.build();

let render_result = scene
.attach_camera(camera)
.with_seed(0)
.render_with_lighting();
let render_result = scene.attach_camera(camera).render_with_lighting();

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

for path in render_result
.geometry_paths
.iter()
.chain(render_result.hatch_paths.iter())
{
for path in render_result {
let (p1, p2) = (path.p1, path.p2);
item_group = item_group.add(
svg::node::element::Line::new()
Expand Down
28 changes: 10 additions & 18 deletions pyraydeon/examples/py_sphere.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,9 @@


class PySphere(Geometry):
def __init__(self, point, radius, material=None):
if material is not None:
self._material = material

def __init__(self, point, radius):
self.sphere = Sphere(point, radius)

@property
def material(self):
return self._material

def collision_geometry(self):
return [self.sphere]

Expand Down Expand Up @@ -71,16 +64,9 @@ def paths(self, cam):


class PyPlane(Geometry):
def __init__(self, point, normal, material=None):
if material is not None:
self._material = material

def __init__(self, point, normal):
self.plane = Plane(point, normal)

@property
def material(self):
return self._material

def collision_geometry(self):
return [self.plane]

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

scene = Scene(
geometry=[
PySphere(Point3(0, 0, 0), 1.0, Material(3.0, 3.0, 3)),
PyPlane(Point3(0, -2, 0), Vec3(0, 1, 0), Material(9000.0, 3.0, 3)),
PySphere(
Point3(0, 0, 0),
1.0,
).with_material(Material(3.0, 3.0, 3)),
PyPlane(
Point3(0, -2, 0),
Vec3(0, 1, 0),
).with_material(Material(9000.0, 3.0, 3)),
],
lights=[PointLight((4, 3, 10), 3.6, 2.0, 0.15, 0.4, 0.11)],
ambient_light=0.13,
Expand Down
192 changes: 192 additions & 0 deletions pyraydeon/src/drawables.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
use std::sync::Arc;

use numpy::{Ix1, PyArray};
use pyo3::prelude::*;

use crate::material::Material;
use crate::shapes::Geometry;

#[derive(Debug)]
#[pyclass(frozen)]
/// A `DrawableShape` is the input geometry for a pyraydeon scene.
///
/// It is essentially some drawable geometry joined with a given material.
pub(crate) struct DrawableShape {
pub raydeon_drawable: raydeon::DrawableShape,
pub pyobj: PyObject,
}

impl ::std::ops::Deref for DrawableShape {
type Target = raydeon::DrawableShape;

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

impl DrawableShape {
pub(crate) fn raydeon_drawable(&self) -> raydeon::DrawableShape {
self.raydeon_drawable.clone()
}
}

impl<'py> FromPyObject<'py> for DrawableShape {
fn extract_bound(obj: &Bound<'py, PyAny>) -> PyResult<Self> {
let py = obj.py();
let shape: PyObject = obj.getattr("shape")?.extract()?;
let material: Option<Material> = obj.getattr("material")?.extract()?;

let raydeon_geometry = raydeon_geometry_from_py_object(py, &shape)?;
let raydeon_drawable = raydeon::DrawableShape::new()
.geometry(raydeon_geometry)
.maybe_material(material.map(|m| m.0))
.build();

Ok(DrawableShape {
raydeon_drawable,
pyobj: shape,
})
}
}

#[pymethods]
impl DrawableShape {
#[new]
#[pyo3(signature = (geometry, material=None))]
fn new(py: Python, geometry: PyObject, material: Option<Material>) -> PyResult<Self> {
let raydeon_geometry = raydeon_geometry_from_py_object(py, &geometry)?;
let material = material.map(|m| m.0);
let raydeon_drawable = raydeon::DrawableShape::new()
.geometry(raydeon_geometry)
.maybe_material(material)
.build();

Ok(DrawableShape {
raydeon_drawable,
pyobj: geometry,
})
}

#[getter]
fn material(&self) -> Option<Material> {
self.raydeon_drawable.material().map(Into::into)
}

#[getter]
fn shape(&self, py: Python) -> PyObject {
self.pyobj.clone_ref(py)
}

fn collision_geometry(&self, py: Python) -> PyResult<PyObject> {
self.pyobj.call_method0(py, "collision_geometry")
}

fn paths(&self, py: Python) -> PyResult<PyObject> {
self.pyobj.call_method0(py, "paths")
}

fn __repr__(slf: &Bound<'_, Self>) -> PyResult<String> {
let class_name = slf.get_type().qualname()?;
Ok(format!("{}<{:#?}>", class_name, slf.borrow()))
}
}

pub(crate) fn raydeon_geometry_from_py_object(
py: Python,
g: &PyObject,
) -> PyResult<Arc<dyn raydeon::Shape>> {
let geom: Py<Geometry> = g.extract(py)?;
let raydeon_shape = geom.borrow(py);
let raydeon_shape = raydeon_shape.geometry(g.clone_ref(py));
Ok(raydeon_shape)
}

#[derive(Debug)]
#[pyclass(frozen)]
/// The output of a raydeon render.
///
/// A line segment, associated with a shape.
pub(crate) struct DrawableSegment {
p1: [f64; 2],
p2: [f64; 2],
kind: SegmentKind,

raydeon_drawable: Option<raydeon::DrawableShape>,
}

impl From<raydeon::DrawableSegment<'_>> for DrawableSegment {
fn from(value: raydeon::DrawableSegment<'_>) -> Self {
let p1 = value.p1.to_array();
let p2 = value.p2.to_array();

let raydeon_drawable = value.segment.get_shape().cloned();

let kind = value.kind.into();

Self {
p1,
p2,
raydeon_drawable,
kind,
}
}
}

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

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

#[getter]
fn material(&self) -> Option<Material> {
self.raydeon_drawable
.as_ref()
.and_then(|d| d.material())
.map(|m| m.into())
}

#[getter]
fn kind(&self) -> SegmentKind {
self.kind
}

fn __repr__(slf: &Bound<'_, Self>) -> PyResult<String> {
let class_name = slf.get_type().qualname()?;
Ok(format!("{}<{:#?}>", class_name, slf.borrow()))
}
}

#[pyclass(eq)]
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub(crate) enum SegmentKind {
VerticalHatch,
DiagonalHatch,
Path,
}

impl From<raydeon::SegmentKind> for SegmentKind {
fn from(value: raydeon::SegmentKind) -> Self {
match value {
raydeon::SegmentKind::ScreenSpaceHatch(raydeon::ScreenSpaceHatchKind::Vertical) => {
SegmentKind::VerticalHatch
}
raydeon::SegmentKind::ScreenSpaceHatch(raydeon::ScreenSpaceHatchKind::Diagonal60) => {
SegmentKind::VerticalHatch
}
raydeon::SegmentKind::Path => SegmentKind::Path,
}
}
}

pub(crate) fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<DrawableShape>()?;
m.add_class::<DrawableSegment>()?;
Ok(())
}
2 changes: 2 additions & 0 deletions pyraydeon/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,7 @@ fn pyraydeon(m: &Bound<'_, PyModule>) -> PyResult<()> {
crate::material::register(m)?;
crate::light::register(m)?;

crate::drawables::register(m)?;

Ok(())
}
8 changes: 7 additions & 1 deletion pyraydeon/src/material.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ impl Material {
#[new]
#[pyo3(signature = (diffuse=0.0, specular=0.0, shininess=0.0, tag=0))]
fn new(diffuse: f64, specular: f64, shininess: f64, tag: usize) -> PyResult<Self> {
Ok(raydeon::material::Material::new(diffuse, specular, shininess, tag).into())
Ok(raydeon::material::Material::new()
.diffuse(diffuse)
.specular(specular)
.shininess(shininess)
.tag(tag)
.build()
.into())
}

#[getter]
Expand Down
Loading

0 comments on commit bb6402c

Please sign in to comment.