diff --git a/.github/workflows/pyraydeon-ci.yml b/.github/workflows/pyraydeon-ci.yml new file mode 100644 index 0000000..72d38f0 --- /dev/null +++ b/.github/workflows/pyraydeon-ci.yml @@ -0,0 +1,189 @@ +# This file is autogenerated by maturin v1.7.4 +# To update, run +# +# maturin generate-ci github +# +name: CI + +on: + push: + branches: + - "*" + tags: + - "*" + pull_request: + workflow_dispatch: + +permissions: + contents: read + +defaults: + run: + working-directory: pyraydeon + +jobs: + linux: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: ubuntu-latest + target: x86_64 + - runner: ubuntu-latest + target: x86 + - runner: ubuntu-latest + target: aarch64 + - runner: ubuntu-latest + target: armv7 + - runner: ubuntu-latest + target: s390x + - runner: ubuntu-latest + target: ppc64le + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.9 + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: "true" + manylinux: auto + working-directory: pyraydeon + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-linux-${{ matrix.platform.target }} + path: dist + + musllinux: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: ubuntu-latest + target: x86_64 + - runner: ubuntu-latest + target: x86 + - runner: ubuntu-latest + target: aarch64 + - runner: ubuntu-latest + target: armv7 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.9 + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: "true" + manylinux: musllinux_1_2 + working-directory: pyraydeon + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-musllinux-${{ matrix.platform.target }} + path: dist + + windows: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: windows-latest + target: x64 + - runner: windows-latest + target: x86 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.9 + architecture: ${{ matrix.platform.target }} + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: "true" + working-directory: pyraydeon + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-windows-${{ matrix.platform.target }} + path: dist + + macos: + runs-on: ${{ matrix.platform.runner }} + strategy: + matrix: + platform: + - runner: macos-12 + target: x86_64 + - runner: macos-14 + target: aarch64 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.9 + - name: Build wheels + uses: PyO3/maturin-action@v1 + with: + target: ${{ matrix.platform.target }} + args: --release --out dist --find-interpreter + sccache: "true" + working-directory: pyraydeon + - name: Upload wheels + uses: actions/upload-artifact@v4 + with: + name: wheels-macos-${{ matrix.platform.target }} + path: dist + + sdist: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Build sdist + uses: PyO3/maturin-action@v1 + with: + command: sdist + args: --out dist + working-directory: pyraydeon + - name: Upload sdist + uses: actions/upload-artifact@v4 + with: + name: wheels-sdist + path: dist + + # release: + # name: Release + # runs-on: ubuntu-latest + # if: ${{ startsWith(github.ref, 'refs/tags/pyraydeon-v') || github.event_name == 'workflow_dispatch' }} + # needs: [linux, musllinux, windows, macos, sdist] + # permissions: + # # Use to sign the release artifacts + # id-token: write + # # Used to upload release artifacts + # contents: write + # # Used to generate artifact attestation + # attestations: write + # steps: + # - uses: actions/download-artifact@v4 + # - name: Generate artifact attestation + # uses: actions/attest-build-provenance@v1 + # with: + # subject-path: "wheels-*/*" + # - name: Publish to PyPI + # if: "startsWith(github.ref, 'refs/tags/')" + # uses: PyO3/maturin-action@v1 + # env: + # MATURIN_PYPI_TOKEN: ${{ secrets.PYPI_API_TOKEN }} + # with: + # command: upload + # args: --non-interactive --skip-existing wheels-*/* diff --git a/.gitignore b/.gitignore index 9b980af..6d572ee 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ Cargo.lock build target *.svg +todo.txt diff --git a/Cargo.toml b/Cargo.toml index 61ccd74..c02c497 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,19 +1,22 @@ -[package] -name = "raydeon" -version = "0.1.0" -authors = ["cbgbt "] -edition = "2018" +[workspace] +resolver = "2" +members = [ + "raydeon", + "pyraydeon", +] -[dependencies] + +[workspace.dependencies] +raydeon = { path = "./raydeon", version = "0.1" } +pyrayeon = { path = "./pyraydeon", version = "0.1" } + +anyhow = "1" cgmath = "0.17" collision = "0.20" +env_logger = "0.11" euclid = "0.22" +float-cmp = "0.5" log = "0.4" rayon = "1.2" -tracing = { version = "0.1", features = ["log"] } - -[dev-dependencies] -anyhow = "1" -env_logger = "0.11" -float-cmp = "0.5" svg = "0.18" +tracing = "0.1" diff --git a/pyraydeon/.gitignore b/pyraydeon/.gitignore new file mode 100644 index 0000000..c8f0442 --- /dev/null +++ b/pyraydeon/.gitignore @@ -0,0 +1,72 @@ +/target + +# Byte-compiled / optimized / DLL files +__pycache__/ +.pytest_cache/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +.venv/ +env/ +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +include/ +man/ +venv/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt +pip-selfcheck.json + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Django stuff: +*.log +*.pot + +.DS_Store + +# Sphinx documentation +docs/_build/ + +# PyCharm +.idea/ + +# VSCode +.vscode/ + +# Pyenv +.python-version diff --git a/pyraydeon/Cargo.toml b/pyraydeon/Cargo.toml new file mode 100644 index 0000000..b153e95 --- /dev/null +++ b/pyraydeon/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "pyraydeon" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[lib] +name = "pyraydeon" +crate-type = ["cdylib"] + +[dependencies] +raydeon.workspace = true + +pyo3 = { version = "0.23", features = ["extension-module"] } diff --git a/pyraydeon/pyproject.toml b/pyraydeon/pyproject.toml new file mode 100644 index 0000000..2c286e5 --- /dev/null +++ b/pyraydeon/pyproject.toml @@ -0,0 +1,15 @@ +[build-system] +requires = ["maturin>=1.7,<2.0"] +build-backend = "maturin" + +[project] +name = "pyraydeon" +requires-python = ">=3.8" +classifiers = [ + "Programming Language :: Rust", + "Programming Language :: Python :: Implementation :: CPython", + "Programming Language :: Python :: Implementation :: PyPy", +] +dynamic = ["version"] +[tool.maturin] +features = ["pyo3/extension-module"] diff --git a/pyraydeon/src/lib.rs b/pyraydeon/src/lib.rs new file mode 100644 index 0000000..4df468f --- /dev/null +++ b/pyraydeon/src/lib.rs @@ -0,0 +1,38 @@ +use pyo3::prelude::*; + +macro_rules! pywrap { + ($name:ident, $wraps:ty) => { + #[derive(Debug, Clone)] + #[pyclass(frozen)] + pub(crate) struct $name(pub(crate) $wraps); + + impl ::std::ops::Deref for $name { + type Target = $wraps; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + impl From<$wraps> for $name { + fn from(value: $wraps) -> Self { + Self(value) + } + } + }; +} + +mod linear; +mod ray; +mod scene; +mod shapes; + +/// A Python module implemented in Rust. +#[pymodule] +fn pyraydeon(m: &Bound<'_, PyModule>) -> PyResult<()> { + crate::linear::register(m)?; + crate::shapes::register(m)?; + crate::scene::register(m)?; + crate::ray::register(m)?; + Ok(()) +} diff --git a/pyraydeon/src/linear.rs b/pyraydeon/src/linear.rs new file mode 100644 index 0000000..846d73f --- /dev/null +++ b/pyraydeon/src/linear.rs @@ -0,0 +1,183 @@ +use pyo3::prelude::*; + +#[derive(Debug, Copy, Clone)] +pub struct ArbitrarySpace; + +pywrap!(Vec3, raydeon::Vec3); + +#[pymethods] +impl Vec3 { + #[new] + fn new(x: f64, y: f64, z: f64) -> Self { + raydeon::Vec3::new(x, y, z).into() + } + + fn as_point(slf: PyRef<'_, Self>) -> PyResult> { + Py::new(slf.py(), Point3(slf.0.to_point())) + } + + #[getter] + fn x(&self) -> f64 { + self.0.x + } + + #[getter] + fn y(&self) -> f64 { + self.0.y + } + + #[getter] + fn z(&self) -> f64 { + self.0.z + } + + fn __repr__(slf: &Bound<'_, Self>) -> PyResult { + let class_name = slf.get_type().qualname()?; + Ok(format!("{}<{:?}>", class_name, slf.borrow().0)) + } + + fn __iter__(slf: PyRef<'_, Self>) -> PyResult> { + let iter = FloatIter { + iter: Box::new([slf.0.x, slf.0.y, slf.0.z].into_iter()), + }; + Py::new(slf.py(), iter) + } + + fn __add__(slf: &Bound<'_, Self>, other: &Bound<'_, Self>) -> Self { + let slf = slf.borrow(); + let other = other.borrow(); + (slf.0 + other.0).into() + } + + fn __sub__(slf: &Bound<'_, Self>, other: &Bound<'_, Self>) -> Self { + let slf = slf.borrow(); + let other = other.borrow(); + (slf.0 - other.0).into() + } + + fn __mul__(slf: &Bound<'_, Self>, other: f64) -> Self { + let slf = slf.borrow(); + (slf.0 * other).into() + } +} + +pywrap!(Point3, raydeon::Point3); + +#[pymethods] +impl Point3 { + #[new] + fn new(x: f64, y: f64, z: f64) -> Self { + raydeon::Point3::new(x, y, z).into() + } + + #[getter] + fn x(&self) -> f64 { + self.0.x + } + + #[getter] + fn y(&self) -> f64 { + self.0.y + } + + #[getter] + fn z(&self) -> f64 { + self.0.z + } + + fn as_vec(slf: PyRef<'_, Self>) -> PyResult> { + Py::new(slf.py(), Vec3(slf.0.to_vector())) + } + + fn __iter__(slf: PyRef<'_, Self>) -> PyResult> { + let iter = FloatIter { + iter: Box::new([slf.0.x, slf.0.y, slf.0.z].into_iter()), + }; + Py::new(slf.py(), iter) + } + + fn __repr__(slf: &Bound<'_, Self>) -> PyResult { + let class_name = slf.get_type().qualname()?; + Ok(format!("{}<{:?}>", class_name, slf.borrow().0)) + } +} + +pywrap!(Point2, raydeon::Point2); + +#[pymethods] +impl Point2 { + #[new] + fn new(x: f64, y: f64) -> Self { + raydeon::Point2::new(x, y).into() + } + + #[getter] + fn x(&self) -> f64 { + self.0.x + } + + #[getter] + fn y(&self) -> f64 { + self.0.y + } + + fn __iter__(slf: PyRef<'_, Self>) -> PyResult> { + let iter = FloatIter { + iter: Box::new([slf.0.x, slf.0.y].into_iter()), + }; + Py::new(slf.py(), iter) + } + + fn __repr__(slf: &Bound<'_, Self>) -> PyResult { + let class_name = slf.get_type().qualname()?; + Ok(format!("{}<{:?}>", class_name, slf.borrow().0)) + } +} + +#[pyclass] +struct FloatIter { + iter: Box + Send + Sync>, +} + +#[pymethods] +impl FloatIter { + fn __iter__(slf: PyRef<'_, Self>) -> PyRef<'_, Self> { + slf + } + + fn __next__(mut slf: PyRefMut<'_, Self>) -> Option { + slf.iter.next() + } +} + +pywrap!(AABB3, raydeon::AABB3); + +#[pymethods] +impl AABB3 { + #[new] + fn new(min: &Point3, max: &Point3) -> Self { + raydeon::AABB3::new(min.0, max.0).into() + } + + #[getter] + fn min(&self) -> Point3 { + self.0.min.into() + } + + #[getter] + fn max(&self) -> Point3 { + self.0.max.into() + } + + fn __repr__(slf: &Bound<'_, Self>) -> PyResult { + let class_name = slf.get_type().qualname()?; + Ok(format!("{}<{:?}>", class_name, slf.borrow().0)) + } +} + +pub(crate) fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + Ok(()) +} diff --git a/pyraydeon/src/ray.rs b/pyraydeon/src/ray.rs new file mode 100644 index 0000000..5ef9176 --- /dev/null +++ b/pyraydeon/src/ray.rs @@ -0,0 +1,59 @@ +use pyo3::prelude::*; + +use crate::linear::{Point3, Vec3}; + +pywrap!(Ray, raydeon::Ray); + +#[pymethods] +impl Ray { + #[new] + fn new(point: &Point3, dir: &Vec3) -> Self { + raydeon::Ray::new(point.0.cast_unit(), dir.0.cast_unit()).into() + } + + #[getter] + fn point(&self) -> Point3 { + self.0.point.cast_unit().into() + } + + #[getter] + fn dir(&self) -> Vec3 { + self.0.dir.cast_unit().into() + } + + fn __repr__(slf: &Bound<'_, Self>) -> PyResult { + let class_name = slf.get_type().qualname()?; + Ok(format!("{}<{:?}>", class_name, slf.borrow().0)) + } +} + +pywrap!(HitData, raydeon::HitData); + +#[pymethods] +impl HitData { + #[new] + fn new(hit_point: &Point3, dist_to: f64) -> Self { + raydeon::HitData::new(hit_point.0.cast_unit(), dist_to).into() + } + + #[getter] + fn hit_point(&self) -> Point3 { + self.0.hit_point.cast_unit().into() + } + + #[getter] + fn dist_to(&self) -> f64 { + self.0.dist_to + } + + fn __repr__(slf: &Bound<'_, Self>) -> PyResult { + let class_name = slf.get_type().qualname()?; + Ok(format!("{}<{:?}>", class_name, slf.borrow().0)) + } +} + +pub(crate) fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + m.add_class::()?; + Ok(()) +} diff --git a/pyraydeon/src/scene.rs b/pyraydeon/src/scene.rs new file mode 100644 index 0000000..d465e9b --- /dev/null +++ b/pyraydeon/src/scene.rs @@ -0,0 +1,121 @@ +use std::sync::Arc; + +use pyo3::prelude::*; +use raydeon::WorldSpace; + +use crate::linear::{ArbitrarySpace, Point2, Point3, Vec3}; +use crate::shapes::Geometry; + +pywrap!(Camera, raydeon::Camera); + +#[pymethods] +impl Camera { + #[staticmethod] + fn look_at(eye: &Point3, center: &Vec3, up: &Vec3) -> LookingCamera { + raydeon::Camera::look_at(eye.cast_unit(), center.cast_unit(), up.cast_unit()).into() + } + + fn __repr__(slf: &Bound<'_, Self>) -> PyResult { + let class_name = slf.get_type().qualname()?; + Ok(format!("{}<{:?}>", class_name, slf.borrow().0)) + } +} + +pywrap!(LookingCamera, raydeon::scene::LookingCamera); + +#[pymethods] +impl LookingCamera { + fn perspective(&self, fovy: f64, width: f64, height: f64, znear: f64, zfar: f64) -> Camera { + self.0.perspective(fovy, width, height, znear, zfar).into() + } + + 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, +} + +#[pymethods] +impl Scene { + #[new] + fn new(py: Python, geometry: Vec) -> PyResult { + let geometry: Vec>> = geometry + .into_iter() + .map(|g| { + let geom: Py = g.extract(py)?; + let raydeon_shape = geom.borrow(py); + let raydeon_shape = raydeon_shape.geometry(g); + Ok(raydeon_shape) + }) + .collect::>()?; + let scene = Arc::new(raydeon::Scene::new(geometry)); + Ok(Self { scene }) + } + + fn render<'py>(&self, py: Python<'py>, camera: &Camera) -> Vec { + py.allow_threads(|| { + let cam = self.scene.attach_camera(camera.0); + cam.render() + .into_iter() + .map(|ls| ls.cast_unit().into()) + .collect() + }) + } + + fn __repr__(slf: &Bound<'_, Self>) -> PyResult { + let class_name = slf.get_type().qualname()?; + Ok(format!("{}<{:?}>", class_name, slf.borrow().scene)) + } +} + +pywrap!(LineSegment2D, raydeon::path::LineSegment2D); + +#[pymethods] +impl LineSegment2D { + #[getter] + fn p1(&self) -> Point2 { + self.p1.cast_unit().into() + } + + #[getter] + fn p2(&self) -> Point2 { + self.p2.cast_unit().into() + } + + fn __repr__(slf: &Bound<'_, Self>) -> PyResult { + let class_name = slf.get_type().qualname()?; + Ok(format!("{}<{:?}>", class_name, slf.borrow().0)) + } +} + +pywrap!(LineSegment3D, raydeon::path::LineSegment3D); + +#[pymethods] +impl LineSegment3D { + #[getter] + fn p1(&self) -> Point3 { + self.p1.cast_unit().into() + } + + #[getter] + fn p2(&self) -> Point3 { + self.p2.cast_unit().into() + } + + fn __repr__(slf: &Bound<'_, Self>) -> PyResult { + let class_name = slf.get_type().qualname()?; + Ok(format!("{}<{:?}>", class_name, slf.borrow().0)) + } +} + +pub(crate) fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + // `LookingCamera` remains "private" + m.add_class::()?; + Ok(()) +} diff --git a/pyraydeon/src/shapes/mod.rs b/pyraydeon/src/shapes/mod.rs new file mode 100644 index 0000000..9e524d0 --- /dev/null +++ b/pyraydeon/src/shapes/mod.rs @@ -0,0 +1,146 @@ +use pyo3::prelude::*; +use pyo3::types::{PyDict, PyTuple}; +use raydeon::WorldSpace; +use std::sync::Arc; + +mod primitive; + +pub(crate) use primitive::{RectPrism, Tri}; + +use crate::linear::AABB3; +use crate::ray::{HitData, Ray}; +use crate::scene::{Camera, LineSegment3D}; + +#[derive(Debug)] +enum InnerGeometry { + Native(Arc>), + Py, +} + +#[derive(Debug)] +#[pyclass(subclass)] +pub(crate) struct Geometry { + geom: InnerGeometry, +} + +impl Geometry { + pub(crate) fn native(geom: Arc>) -> Self { + let geom = InnerGeometry::Native(geom); + Self { geom } + } + + pub(crate) fn py() -> Self { + let geom = InnerGeometry::Py; + Self { geom } + } + + pub(crate) fn geometry(&self, obj: PyObject) -> Arc> { + match &self.geom { + InnerGeometry::Native(ref geom) => Arc::clone(geom), + InnerGeometry::Py => Arc::new(PythonGeometry { slf: obj }), + } + } +} + +#[pymethods] +impl Geometry { + #[new] + #[pyo3(signature = (*_py_args, **_py_kwargs))] + fn new(_py_args: &Bound<'_, PyTuple>, _py_kwargs: Option<&Bound<'_, PyDict>>) -> Self { + Self::py() + } + + fn hit_by(&self, ray: &Ray) -> Option { + match self.geom { + InnerGeometry::Native(ref geom) => geom.hit_by(&ray.0).map(Into::into), + InnerGeometry::Py => None, + } + } + + fn paths(&self, cam: &Camera) -> Vec { + match &self.geom { + InnerGeometry::Native(geom) => { + let paths = geom.paths(&cam.0); + paths + .into_iter() + .map(raydeon::path::LineSegment3D::cast_unit) + .map(Into::into) + .collect() + } + InnerGeometry::Py => Vec::new(), + } + } + + fn bounding_box(&self) -> Option { + match &self.geom { + InnerGeometry::Native(geom) => geom + .bounding_box() + .as_ref() + .map(raydeon::AABB3::cast_unit) + .map(Into::into), + InnerGeometry::Py => None, + } + } + + fn __repr__(slf: &Bound<'_, Self>) -> PyResult { + let class_name = slf.get_type().qualname()?; + Ok(format!("{}<{:?}>", class_name, slf.borrow().geom)) + } +} + +#[derive(Debug)] +struct PythonGeometry { + slf: PyObject, +} + +impl raydeon::Shape for PythonGeometry { + fn hit_by(&self, ray: &raydeon::Ray) -> Option { + Python::with_gil(|py| { + let inner = self.slf.bind_borrowed(py); + let ray = Ray::from(*ray); + let call_result = inner.call_method1("hit_by", (ray,)).ok()?; + + call_result + .extract::>() + .ok()? + .map(|hit| hit.0) + }) + } + + fn paths(&self, cam: &raydeon::Camera) -> Vec> { + let segments: Option<_> = Python::with_gil(|py| { + let inner = self.slf.bind_borrowed(py); + let cam = Camera::from(*cam); + let call_result = inner.call_method1("paths", (cam,)).ok()?; + + let segments = call_result.extract::>().ok()?; + + Some( + segments + .into_iter() + .map(|segment| segment.0.cast_unit()) + .collect(), + ) + }); + segments.unwrap_or_default() + } + + fn bounding_box(&self) -> Option> { + Python::with_gil(|py| { + let inner = self.slf.bind_borrowed(py); + let call_result = inner.call_method1("hit_by", ()).ok()?; + + call_result + .extract::>() + .ok()? + .map(|aabb| aabb.0.cast_unit()) + }) + } +} + +pub(crate) fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + Ok(()) +} diff --git a/pyraydeon/src/shapes/primitive.rs b/pyraydeon/src/shapes/primitive.rs new file mode 100644 index 0000000..aea88d1 --- /dev/null +++ b/pyraydeon/src/shapes/primitive.rs @@ -0,0 +1,70 @@ +use super::Geometry; +use crate::linear::Point3; +use pyo3::prelude::*; +use raydeon::WorldSpace; +use std::sync::Arc; + +#[pyclass(frozen, extends=Geometry, subclass)] +pub(crate) struct RectPrism(pub(crate) Arc); + +impl ::std::ops::Deref for RectPrism { + type Target = Arc; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From> for RectPrism { + fn from(value: Arc) -> Self { + Self(value) + } +} + +#[pymethods] +impl RectPrism { + #[new] + #[pyo3(signature = (min, max, tag=0))] + fn new(min: &Point3, max: &Point3, tag: usize) -> (Self, Geometry) { + let shape = Arc::new(raydeon::shapes::RectPrism::tagged( + min.to_vector().cast_unit(), + max.to_vector().cast_unit(), + tag, + )); + let geom = Geometry::native(Arc::clone(&shape) as Arc>); + (Self(shape), geom) + } +} + +#[pyclass(frozen, extends=Geometry, subclass)] +pub(crate) struct Tri(pub(crate) Arc); + +impl ::std::ops::Deref for Tri { + type Target = Arc; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl From> for Tri { + fn from(value: Arc) -> Self { + Self(value) + } +} + +#[pymethods] +impl Tri { + #[new] + #[pyo3(signature = (p1, p2, p3, tag=0))] + fn new(p1: &Point3, p2: &Point3, p3: &Point3, tag: usize) -> (Self, Geometry) { + let shape = Arc::new(raydeon::shapes::Triangle::tagged( + p1.cast_unit(), + p2.cast_unit(), + p3.cast_unit(), + tag, + )); + let geom = Geometry::native(Arc::clone(&shape) as Arc>); + (Self(shape), geom) + } +} diff --git a/pyraydeon/test.py b/pyraydeon/test.py new file mode 100644 index 0000000..152877f --- /dev/null +++ b/pyraydeon/test.py @@ -0,0 +1,76 @@ +import svg + +from pyraydeon import Camera, Point3, Scene, Tri, Vec3, Geometry + + +class CustomObject(Geometry): + def __init__(self, p1, p2, p3): + self.tri = Tri(p1, p2, p3) + + def hit_by(self, ray): + return self.tri.hit_by(ray) + + def paths(self, cam): + return self.tri.paths(cam) + + def bounding_box(self): + return self.tri.bounding_box() + + +scene = Scene( + [ + CustomObject( + Point3(0, 0, 0), + Point3(0, 0, 1), + Point3(1, 0, 1), + ), + Tri( + Point3(0.25, 0.25, 0), + Point3(0, 0.25, 1), + Point3(-0.65, 0.25, 1), + ), + ] +) + +eye = Point3(0, 3, 0) +focus = Vec3(0, 0, 0) +up = Vec3(0, 0, 1) + +fovy = 50.0 +width = 1024 +height = 1024 +znear = 0.1 +zfar = 10.0 + +cam = Camera.look_at(eye, focus, up).perspective(fovy, width, height, znear, zfar) + +paths = scene.render(cam) + +canvas = svg.SVG( + width="8in", + height="8in", + viewBox="0 0 1024 1024", +) +backing_rect = svg.Rect( + x=0, + y=0, + width="100%", + height="100%", + fill="white", +) +svg_lines = [ + svg.Line( + x1=f"{path.p1.x}", + y1=f"{path.p1.y}", + x2=f"{path.p2.x}", + y2=f"{path.p2.y}", + stroke_width="0.7mm", + stroke="black", + ) + for path in paths +] +line_group = svg.G(transform=f"translate(0, {height}) scale(1, -1)", elements=svg_lines) +canvas.elements = [backing_rect, line_group] + + +print(canvas) diff --git a/raydeon/Cargo.toml b/raydeon/Cargo.toml new file mode 100644 index 0000000..46fd9f2 --- /dev/null +++ b/raydeon/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "raydeon" +version = "0.1.0" +authors = ["cbgbt "] +edition = "2018" + +[dependencies] +cgmath.workspace = true +collision.workspace = true +euclid.workspace = true +log.workspace = true +rayon.workspace = true +tracing = { workspace = true, features = ["log"] } + +[dev-dependencies] +anyhow.workspace = true +env_logger.workspace = true +float-cmp.workspace = true +svg.workspace = true diff --git a/examples/cityscape.png b/raydeon/examples/cityscape.png similarity index 100% rename from examples/cityscape.png rename to raydeon/examples/cityscape.png diff --git a/examples/cube.png b/raydeon/examples/cube.png similarity index 100% rename from examples/cube.png rename to raydeon/examples/cube.png diff --git a/examples/cube.rs b/raydeon/examples/cube.rs similarity index 96% rename from examples/cube.rs rename to raydeon/examples/cube.rs index 71c9958..9aa0e8a 100644 --- a/examples/cube.rs +++ b/raydeon/examples/cube.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use anyhow::{Context, Result}; use raydeon::shapes::RectPrism; use raydeon::{Camera, Scene, WPoint3, WVec3}; @@ -7,7 +9,7 @@ fn main() -> Result<()> { .format_timestamp_nanos() .init(); - let scene = Scene::new(vec![Box::new(RectPrism::new( + let scene = Scene::new(vec![Arc::new(RectPrism::new( WVec3::new(-1.0, -1.0, -1.0), WVec3::new(1.0, 1.0, 1.0), ))]); diff --git a/examples/geom_perf.rs b/raydeon/examples/geom_perf.rs similarity index 93% rename from examples/geom_perf.rs rename to raydeon/examples/geom_perf.rs index cf79043..65b5abd 100644 --- a/examples/geom_perf.rs +++ b/raydeon/examples/geom_perf.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use anyhow::{Context, Result}; use raydeon::shapes::RectPrism; use raydeon::{Camera, Scene, Shape, WPoint3, WVec3, WorldSpace}; @@ -69,8 +71,8 @@ const LENGTH: usize = 100; const CELL_WIDTH: f64 = 2.0; const CELL_LENGTH: f64 = 3.0; -fn generate_scene() -> Vec>> { - let mut scene: Vec>> = Vec::new(); +fn generate_scene() -> Vec>> { + let mut scene: Vec>> = Vec::new(); for i in 0..WIDTH { for j in 0..LENGTH { @@ -81,7 +83,7 @@ fn generate_scene() -> Vec>> { let z1 = cell_z + 0.15; let z2 = cell_z + CELL_LENGTH - 0.15; - scene.push(Box::new(RectPrism::new( + scene.push(Arc::new(RectPrism::new( WVec3::new(x1, 0.0, z1), WVec3::new(x2, 2.5, z2), ))); diff --git a/examples/triangles.rs b/raydeon/examples/triangles.rs similarity index 96% rename from examples/triangles.rs rename to raydeon/examples/triangles.rs index 3e7c71c..e311031 100644 --- a/examples/triangles.rs +++ b/raydeon/examples/triangles.rs @@ -1,3 +1,5 @@ +use std::sync::Arc; + use anyhow::{Context, Result}; use raydeon::shapes::Triangle; use raydeon::{Camera, Scene, WPoint3, WVec3}; @@ -8,12 +10,12 @@ fn main() -> Result<()> { .init(); let scene = Scene::new(vec![ - Box::new(Triangle::new( + Arc::new(Triangle::new( WPoint3::new(0.0, 0.0, 0.0), WPoint3::new(0.0, 0.0, 1.0), WPoint3::new(1.0, 0.0, 1.0), )), - Box::new(Triangle::new( + Arc::new(Triangle::new( WPoint3::new(0.25, 0.25, 0.0), WPoint3::new(0.0, 0.25, 1.0), WPoint3::new(-0.65, 0.25, 1.0), diff --git a/src/bvh.rs b/raydeon/src/bvh.rs similarity index 96% rename from src/bvh.rs rename to raydeon/src/bvh.rs index e02ab0f..087fff6 100644 --- a/src/bvh.rs +++ b/raydeon/src/bvh.rs @@ -1,4 +1,4 @@ -use crate::{HitData, Ray, Shape, WorldSpace, AABB}; +use crate::{HitData, Ray, Shape, WorldSpace, AABB3}; use euclid::Point3D; use rayon::prelude::*; use std::sync::Arc; @@ -16,7 +16,7 @@ pub(crate) struct BVHTree where Space: Copy + Send + Sync + Sized + std::fmt::Debug + 'static, { - aabb: AABB, + aabb: AABB3, root: Option>, unbounded: Vec>>, } @@ -308,14 +308,14 @@ where Space: Copy + Send + Sync + Sized + std::fmt::Debug + 'static, { shape: Arc>, - aabb: AABB, + aabb: AABB3, } -fn bounding_box_for_shapes(shapes: &[Arc>]) -> AABB +fn bounding_box_for_shapes(shapes: &[Arc>]) -> AABB3 where Space: Copy + Send + Sync + Sized + std::fmt::Debug + 'static, { - let aabb = AABB::new(Point3D::splat(f64::MAX), Point3D::splat(f64::MIN)); + let aabb = AABB3::new(Point3D::splat(f64::MAX), Point3D::splat(f64::MIN)); let bounding_boxes = shapes.iter().map(|shape| shape.aabb).collect::>(); bounding_boxes.into_par_iter().reduce( @@ -324,12 +324,12 @@ where let min = a.min.min(b.min); let max = a.max.max(b.max); - AABB::new(min, max) + AABB3::new(min, max) }, ) } -fn partition_bounding_box(axis: Axis, aabb: AABB, point: f64) -> (bool, bool) +fn partition_bounding_box(axis: Axis, aabb: AABB3, point: f64) -> (bool, bool) where Space: Copy + Send + Sync + Sized + std::fmt::Debug + 'static, { @@ -340,7 +340,7 @@ where } } -fn bounding_box_intersects(aabb: AABB, ray: Ray) -> (f64, f64) { +fn bounding_box_intersects(aabb: AABB3, ray: Ray) -> (f64, f64) { let v1 = (aabb.min - ray.point).component_div(ray.dir); let v2 = (aabb.max - ray.point).component_div(ray.dir); diff --git a/raydeon/src/lib.rs b/raydeon/src/lib.rs new file mode 100644 index 0000000..a2dd48f --- /dev/null +++ b/raydeon/src/lib.rs @@ -0,0 +1,49 @@ +pub(crate) mod bvh; +pub mod path; +pub mod ray; +pub mod scene; +pub mod shapes; + +use path::LineSegment3D; +pub use ray::{HitData, Ray}; + +pub use scene::{Camera, Scene}; + +#[cfg(test)] +pub(crate) static EPSILON: f64 = 0.004; + +#[derive(Debug, Copy, Clone)] +pub struct WorldSpace; +#[derive(Debug, Copy, Clone)] +pub struct CameraSpace; + +#[derive(Debug, Copy, Clone)] +pub struct CanvasSpace; + +pub type Point2 = euclid::Point2D; +pub type Point3 = euclid::Point3D; +pub type Vec2 = euclid::Vector2D; +pub type Vec3 = euclid::Vector3D; +pub type Transform2 = euclid::Transform2D; +pub type Transform3 = euclid::Transform3D; +pub type AABB3 = euclid::Box3D; + +pub type WVec3 = Vec3; +pub type WPoint3 = Point3; + +pub type CVec3 = Vec3; +pub type CPoint3 = Point3; + +pub type CWTransform = Transform3; +pub type WCTransform = Transform3; +pub type WWTransform = Transform3; +pub type CCTransform = Transform3; + +pub trait Shape: Send + Sync + std::fmt::Debug +where + Space: Sized + Send + Sync + std::fmt::Debug + Copy + Clone, +{ + fn hit_by(&self, ray: &Ray) -> Option; + fn paths(&self, cam: &Camera) -> Vec>; + fn bounding_box(&self) -> Option>; +} diff --git a/src/path.rs b/raydeon/src/path.rs similarity index 52% rename from src/path.rs rename to raydeon/src/path.rs index a43adb3..79918db 100644 --- a/src/path.rs +++ b/raydeon/src/path.rs @@ -2,7 +2,7 @@ use euclid::approxeq::ApproxEq; use euclid::*; #[derive(Debug, Copy, Clone)] -pub struct LineSegment +pub struct LineSegment3D where Space: Copy + Clone + std::fmt::Debug, { @@ -11,7 +11,7 @@ where pub tag: usize, } -impl LineSegment +impl LineSegment3D where Space: Copy + Clone + std::fmt::Debug, { @@ -19,6 +19,25 @@ where Self::tagged(p1, p2, 0) } + pub fn cast_unit(self) -> LineSegment3D + where + U: Copy + Clone + std::fmt::Debug, + { + LineSegment3D::new(self.p1.cast_unit(), self.p2.cast_unit()) + } + + pub fn xy(self) -> LineSegment2D { + LineSegment2D::new(self.p1.xy(), self.p2.xy()) + } + + pub fn yz(self) -> LineSegment2D { + LineSegment2D::new(self.p1.yz(), self.p2.yz()) + } + + pub fn xz(self) -> LineSegment2D { + LineSegment2D::new(self.p1.xz(), self.p2.xz()) + } + pub fn tagged(p1: Point3D, p2: Point3D, tag: usize) -> Self { Self { p1, p2, tag } } @@ -26,24 +45,24 @@ where pub fn transform( &self, transformation: &Transform3D, - ) -> Option> + ) -> Option> where Dst: Copy + Clone + std::fmt::Debug, { 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| LineSegment::tagged(p1, p2, self.tag))) + p1t.and_then(|p1| p2t.map(|p2| LineSegment3D::tagged(p1, p2, self.tag))) } } -pub fn simplify_segments(paths: &[LineSegment], threshold: f64) -> Vec> +pub fn simplify_3d_segments(paths: &[LineSegment3D], threshold: f64) -> Vec> where T: Copy + Clone + std::fmt::Debug, { let eps: Point3D = Point3D::new(threshold, threshold, threshold); let mut npaths = Vec::new(); - let mut curr_line: Option> = None; + let mut curr_line: Option> = None; let mut curr_pushed = true; for path in paths { let v1 = path.p1; @@ -66,20 +85,20 @@ where if same_tag && same_dir { if cv1.approx_eq_eps(&v1, &eps) { - curr_line = Some(LineSegment::tagged(v2, cv2, path.tag)); + curr_line = Some(LineSegment3D::tagged(v2, cv2, path.tag)); curr_pushed = false; } else if cv1.approx_eq_eps(&v2, &eps) { - curr_line = Some(LineSegment::tagged(v1, cv2, path.tag)); + curr_line = Some(LineSegment3D::tagged(v1, cv2, path.tag)); curr_pushed = false; } else if cv2.approx_eq_eps(&v1, &eps) { - curr_line = Some(LineSegment::tagged(v2, cv1, path.tag)); + curr_line = Some(LineSegment3D::tagged(v2, cv1, path.tag)); curr_pushed = false; } else if cv2.approx_eq_eps(&v2, &eps) { - curr_line = Some(LineSegment::tagged(v1, cv1, path.tag)); + curr_line = Some(LineSegment3D::tagged(v1, cv1, path.tag)); curr_pushed = false; } else { npaths.push(curr_line.unwrap()); - curr_line = Some(LineSegment::tagged(v1, v2, path.tag)); + curr_line = Some(LineSegment3D::tagged(v1, v2, path.tag)); curr_pushed = true; } } else { @@ -98,3 +117,48 @@ where npaths } + +#[derive(Debug, Copy, Clone)] +pub struct LineSegment2D +where + Space: Copy + Clone + std::fmt::Debug, +{ + pub p1: Point2D, + pub p2: Point2D, + pub tag: usize, +} + +impl LineSegment2D +where + Space: Copy + Clone + std::fmt::Debug, +{ + pub fn new(p1: Point2D, p2: Point2D) -> Self { + Self::tagged(p1, p2, 0) + } + + pub fn tagged(p1: Point2D, p2: Point2D, tag: usize) -> Self { + Self { p1, p2, tag } + } + + pub fn cast_unit(self) -> LineSegment2D + where + U: Copy + Clone + std::fmt::Debug, + { + LineSegment2D::new(self.p1.cast_unit(), self.p2.cast_unit()) + } + + pub fn transform( + &self, + transformation: &Transform2D, + ) -> LineSegment2D + where + Dst: Copy + Clone + std::fmt::Debug, + { + let (p1, p2) = (self.p1, self.p2); + LineSegment2D::tagged( + transformation.transform_point(p1), + transformation.transform_point(p2), + self.tag, + ) + } +} diff --git a/src/ray.rs b/raydeon/src/ray.rs similarity index 100% rename from src/ray.rs rename to raydeon/src/ray.rs diff --git a/src/scene.rs b/raydeon/src/scene.rs similarity index 90% rename from src/scene.rs rename to raydeon/src/scene.rs index 6006e3c..16c421c 100644 --- a/src/scene.rs +++ b/raydeon/src/scene.rs @@ -1,11 +1,13 @@ use bvh::BVHTree; -use path::simplify_segments; +use euclid::{Transform3D, Vector3D}; +use path::{simplify_3d_segments, LineSegment2D}; use rayon::prelude::*; use std::sync::Arc; use tracing::info; use crate::*; +#[derive(Debug, Copy, Clone)] pub struct LookingCamera { eye: WPoint3, center: WVec3, @@ -65,7 +67,10 @@ impl Camera { } /// Chops a line segment into subsegments based on distance from camera - pub fn chop_segment(&self, segment: &LineSegment) -> Vec> { + pub fn chop_segment( + &self, + segment: &LineSegment3D, + ) -> Vec> { // linearly interpolate step_size based on closest point to the camera let p1 = segment.p1.to_vector(); let p2 = segment.p2.to_vector(); @@ -109,7 +114,7 @@ impl Camera { .map(|segment_ndx| { let p1 = segment.p1 + (chunk_vec * (segment_ndx as f64)); let p2 = p1 + chunk_vec; - LineSegment::tagged(p1, p2, segment.tag) + LineSegment3D::tagged(p1, p2, segment.tag) }) .collect() } @@ -190,24 +195,24 @@ impl LookingCamera { pub struct SceneCamera<'s> { camera: Camera, scene: &'s Scene, - paths: Vec>>, + paths: Vec>>, path_count: usize, } impl<'a> SceneCamera<'a> { - fn clip_filter(&self, path: &LineSegment) -> bool { + fn clip_filter(&self, path: &LineSegment3D) -> bool { let (p1, p2) = (path.p1, path.p2); let midpoint = p1 + ((p2 - p1) / 2.0); self.scene.visible(self.camera.eye, midpoint) } - pub fn render(&self) -> Vec> { + pub fn render(&self) -> Vec> { info!( "Clipping occluded and distant segment chunks, started with {} segments", self.path_count ); - let transformation: Transform3D = self + let transformation: Transform3 = self .camera .matrix .then_translate(Vector3D::new(1.0, 1.0, 0.0)) @@ -229,8 +234,9 @@ impl<'a> SceneCamera<'a> { .cloned() .collect::>() }) - .flat_map(|path_group| simplify_segments(&path_group, 1.0e-6)) + .flat_map(|path_group| simplify_3d_segments(&path_group, 1.0e-6)) .filter_map(|path| path.transform(&transformation)) + .map(LineSegment3D::xy) .collect(); info!("{} paths remain after clipping", paths.len()); @@ -246,18 +252,17 @@ pub struct Scene { } impl Scene { - pub fn new(geometry: Vec>>) -> Scene { - let geometry: Vec<_> = geometry.into_iter().map(Arc::from).collect(); + pub fn new(geometry: Vec>>) -> Scene { let bvh = BVHTree::new(&geometry); Scene { geometry, bvh } } pub fn attach_camera(&self, camera: Camera) -> SceneCamera { info!("Caching line segment chunks based on new camera attachment"); - let paths: Vec>> = self + let paths: Vec>> = self .geometry .par_iter() - .map(|s| s.paths()) + .map(|s| s.paths(&camera)) .flat_map(|paths| { paths .par_iter() diff --git a/src/shapes/mod.rs b/raydeon/src/shapes/mod.rs similarity index 100% rename from src/shapes/mod.rs rename to raydeon/src/shapes/mod.rs diff --git a/src/shapes/plane.rs b/raydeon/src/shapes/plane.rs similarity index 89% rename from src/shapes/plane.rs rename to raydeon/src/shapes/plane.rs index 3d1b514..5d2c05e 100644 --- a/src/shapes/plane.rs +++ b/raydeon/src/shapes/plane.rs @@ -1,5 +1,5 @@ -use crate::path::LineSegment; -use crate::{HitData, Ray, Shape, WPoint3, WVec3, WorldSpace}; +use crate::path::LineSegment3D; +use crate::{Camera, HitData, Ray, Shape, WPoint3, WVec3, WorldSpace}; #[derive(Debug, Copy, Clone)] #[cfg_attr(test, derive(PartialEq))] @@ -34,11 +34,11 @@ impl Shape for Plane { Some(HitData::new(hit_point, t)) } - fn paths(&self) -> Vec> { + fn paths(&self, _cam: &Camera) -> Vec> { unimplemented!() } - fn bounding_box(&self) -> Option> { + fn bounding_box(&self) -> Option> { None } } diff --git a/src/shapes/rectprism.rs b/raydeon/src/shapes/rectprism.rs similarity index 71% rename from src/shapes/rectprism.rs rename to raydeon/src/shapes/rectprism.rs index ccd4dd7..b781556 100644 --- a/src/shapes/rectprism.rs +++ b/raydeon/src/shapes/rectprism.rs @@ -1,7 +1,7 @@ use collision::Continuous; -use crate::path::LineSegment; -use crate::{HitData, Ray, Shape, WPoint3, WVec3, WorldSpace, AABB}; +use crate::path::LineSegment3D; +use crate::{Camera, HitData, Ray, Shape, WPoint3, WVec3, WorldSpace, AABB3}; #[derive(Debug, Copy, Clone)] #[cfg_attr(test, derive(PartialEq))] @@ -21,8 +21,8 @@ impl RectPrism { } } -impl From> for RectPrism { - fn from(value: AABB) -> Self { +impl From> for RectPrism { + fn from(value: AABB3) -> Self { Self::new(value.min.to_vector(), value.max.to_vector()) } } @@ -48,7 +48,7 @@ impl Shape for RectPrism { } } - fn paths(&self) -> Vec> { + fn paths(&self, _cam: &Camera) -> Vec> { let expand = (self.max - self.min).normalize() * 0.0015; let pathmin = self.min - expand; let pathmax = self.max + expand; @@ -67,23 +67,23 @@ impl Shape for RectPrism { let p8 = WPoint3::new(x1, y2, z2); vec![ - LineSegment::tagged(p1, p2, self.tag), - LineSegment::tagged(p2, p3, self.tag), - LineSegment::tagged(p3, p4, self.tag), - LineSegment::tagged(p4, p1, self.tag), - LineSegment::tagged(p5, p6, self.tag), - LineSegment::tagged(p6, p7, self.tag), - LineSegment::tagged(p7, p8, self.tag), - LineSegment::tagged(p8, p5, self.tag), - LineSegment::tagged(p1, p5, self.tag), - LineSegment::tagged(p2, p6, self.tag), - LineSegment::tagged(p3, p7, self.tag), - LineSegment::tagged(p4, p8, self.tag), + LineSegment3D::tagged(p1, p2, self.tag), + LineSegment3D::tagged(p2, p3, self.tag), + LineSegment3D::tagged(p3, p4, self.tag), + LineSegment3D::tagged(p4, p1, self.tag), + LineSegment3D::tagged(p5, p6, self.tag), + LineSegment3D::tagged(p6, p7, self.tag), + LineSegment3D::tagged(p7, p8, self.tag), + LineSegment3D::tagged(p8, p5, self.tag), + LineSegment3D::tagged(p1, p5, self.tag), + LineSegment3D::tagged(p2, p6, self.tag), + LineSegment3D::tagged(p3, p7, self.tag), + LineSegment3D::tagged(p4, p8, self.tag), ] } - fn bounding_box(&self) -> Option> { - Some(crate::AABB::new(self.min.to_point(), self.max.to_point())) + fn bounding_box(&self) -> Option> { + Some(crate::AABB3::new(self.min.to_point(), self.max.to_point())) } } diff --git a/src/shapes/sphere.rs b/raydeon/src/shapes/sphere.rs similarity index 91% rename from src/shapes/sphere.rs rename to raydeon/src/shapes/sphere.rs index a07a62d..1648ed1 100644 --- a/src/shapes/sphere.rs +++ b/raydeon/src/shapes/sphere.rs @@ -1,5 +1,5 @@ -use crate::path::LineSegment; -use crate::{HitData, Ray, Shape, WPoint3, WVec3, WorldSpace}; +use crate::path::LineSegment3D; +use crate::{Camera, HitData, Ray, Shape, WPoint3, WVec3, WorldSpace}; #[derive(Debug, Copy, Clone)] #[cfg_attr(test, derive(PartialEq))] @@ -49,14 +49,14 @@ impl Shape for Sphere { Some(HitData::new(hit_point, t)) } - fn paths(&self) -> Vec> { + fn paths(&self, _cam: &Camera) -> Vec> { unimplemented!() } - fn bounding_box(&self) -> Option> { + fn bounding_box(&self) -> Option> { let min = self.center - WVec3::splat(self.radius); let max = self.center + WVec3::splat(self.radius); - Some(crate::AABB::new(min, max)) + Some(crate::AABB3::new(min, max)) } } diff --git a/src/shapes/triangle.rs b/raydeon/src/shapes/triangle.rs similarity index 89% rename from src/shapes/triangle.rs rename to raydeon/src/shapes/triangle.rs index 68e4a76..faf97d8 100644 --- a/src/shapes/triangle.rs +++ b/raydeon/src/shapes/triangle.rs @@ -1,6 +1,6 @@ use super::plane::Plane; -use crate::path::LineSegment; -use crate::{HitData, Ray, Shape, WPoint3, WVec3, WorldSpace}; +use crate::path::LineSegment3D; +use crate::{Camera, HitData, Ray, Shape, WPoint3, WVec3, WorldSpace}; #[derive(Debug, Copy, Clone)] #[cfg_attr(test, derive(PartialEq))] @@ -59,7 +59,7 @@ impl Shape for Triangle { } } - fn paths(&self) -> Vec> { + fn paths(&self, _cam: &Camera) -> Vec> { let v0 = self.verts[0]; let v1 = self.verts[1]; let v2 = self.verts[2]; @@ -70,13 +70,13 @@ impl Shape for Triangle { let v2 = v2 + (v2 - centroid).normalize() * 0.015; vec![ - LineSegment::tagged(v0, v1, self.tag), - LineSegment::tagged(v1, v2, self.tag), - LineSegment::tagged(v2, v0, self.tag), + LineSegment3D::tagged(v0, v1, self.tag), + LineSegment3D::tagged(v1, v2, self.tag), + LineSegment3D::tagged(v2, v0, self.tag), ] } - fn bounding_box(&self) -> Option> { + fn bounding_box(&self) -> Option> { let mut min = WPoint3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY); let mut max = WPoint3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY); @@ -90,7 +90,7 @@ impl Shape for Triangle { max.z = max.z.max(vert.z); } - Some(crate::AABB::new(min, max)) + Some(crate::AABB3::new(min, max)) } } diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index fc2d026..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,44 +0,0 @@ -pub(crate) mod bvh; -pub mod path; -pub mod ray; -pub mod scene; -pub mod shapes; - -use euclid::*; - -use path::LineSegment; -pub use ray::{HitData, Ray}; - -pub use scene::{Camera, Scene}; - -#[cfg(test)] -pub(crate) static EPSILON: f64 = 0.004; - -#[derive(Debug, Copy, Clone)] -pub struct WorldSpace; -#[derive(Debug, Copy, Clone)] -pub struct CameraSpace; - -#[derive(Debug, Copy, Clone)] -pub struct CanvasSpace; - -pub type WVec3 = Vector3D; -pub type WPoint3 = Point3D; -pub type AABB = euclid::Box3D; - -pub type CVec3 = Vector3D; -pub type CPoint3 = Point3D; - -pub type CWTransform = Transform3D; -pub type WCTransform = Transform3D; -pub type WWTransform = Transform3D; -pub type CCTransform = Transform3D; - -pub trait Shape: Send + Sync + std::fmt::Debug -where - Space: Sized + Send + Sync + std::fmt::Debug + Copy + Clone, -{ - fn hit_by(&self, ray: &Ray) -> Option; - fn paths(&self) -> Vec>; - fn bounding_box(&self) -> Option>; -}