From 38e28a2d579de5d8cf15180f20c2654902edca5f Mon Sep 17 00:00:00 2001 From: wires Date: Wed, 23 Apr 2025 12:16:56 -0400 Subject: [PATCH] more geometry stuff, shapes --- Cargo.lock | 13 ++++++ core/Cargo.toml | 1 + core/src/geometry.rs | 58 ++++++++++++++++++++++-- core/src/geometry/shapes.rs | 90 +++++++++++++++++++++++++++++++++++++ core/src/lib.rs | 3 ++ examples/basic.py | 15 +++---- py/src/lib.rs | 23 +++++++++- 7 files changed, 190 insertions(+), 13 deletions(-) create mode 100644 core/src/geometry/shapes.rs mode change 100644 => 100755 examples/basic.py diff --git a/Cargo.lock b/Cargo.lock index 2032c8e..1ca07a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,18 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "enum_dispatch" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa18ce2bc66555b3218614519ac839ddb759a7d6720732f979ef8d13be147ecd" +dependencies = [ + "once_cell", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "glam" version = "0.30.2" @@ -52,6 +64,7 @@ name = "mechthild_core" version = "0.1.0" dependencies = [ "bytemuck", + "enum_dispatch", "glam", ] diff --git a/core/Cargo.toml b/core/Cargo.toml index c7db622..92537ce 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -8,6 +8,7 @@ repository.workspace = true [dependencies] bytemuck = { version = "1.22.0", optional = true } +enum_dispatch = "0.3.13" glam = "0.30.2" [features] diff --git a/core/src/geometry.rs b/core/src/geometry.rs index 9d10280..b319ac9 100644 --- a/core/src/geometry.rs +++ b/core/src/geometry.rs @@ -1,9 +1,24 @@ -use std::ops::{Add, Div, Mul, Sub}; +use std::{ + fmt, + ops::{Add, Div, Mul, Sub}, +}; + +pub mod shapes; #[repr(transparent)] -#[derive(Debug, Clone, Copy)] +#[derive(Clone, Copy)] pub struct Vector(glam::Vec3A); +impl fmt::Debug for Vector { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple(stringify!(Vector)) + .field(&self.0.x) + .field(&self.0.y) + .field(&self.0.z) + .finish() + } +} + impl From for glam::Vec3 { fn from(value: Vector) -> Self { value.0.into() @@ -30,6 +45,10 @@ impl Vector { self.0.length() } + pub fn length_squared(self) -> f32 { + self.0.length_squared() + } + pub fn dot(self, other: Vector) -> f32 { self.0.dot(other.0) } @@ -72,9 +91,19 @@ impl Add for Vector { } #[repr(transparent)] -#[derive(Debug, Clone, Copy)] +#[derive(Clone, Copy)] pub struct Point(glam::Vec3A); +impl fmt::Debug for Point { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple(stringify!(Point)) + .field(&self.0.x) + .field(&self.0.y) + .field(&self.0.z) + .finish() + } +} + impl From for glam::Vec3 { fn from(value: Point) -> Self { value.0.into() @@ -93,6 +122,10 @@ impl Point { pub fn new(x: f32, y: f32, z: f32) -> Self { Self(glam::vec3a(x, y, z)) } + + pub fn to_vector(self) -> Vector { + Vector(self.0) + } } impl Add for Point { @@ -111,10 +144,19 @@ impl Sub for Point { } } -#[derive(Debug, Copy, Clone)] +#[derive(Copy, Clone)] #[repr(transparent)] pub struct Transform(glam::Affine3A); +impl fmt::Debug for Transform { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct(stringify!(Transform)) + .field(stringify!(matrix3), &self.0.matrix3) + .field(stringify!(translation), &self.0.translation) + .finish() + } +} + impl Transform { pub fn scale(x: f32, y: f32, z: f32) -> Self { Self(glam::Affine3A::from_scale(glam::vec3(x, y, z))) @@ -149,6 +191,14 @@ impl Mul for Transform { } } +impl Mul for Transform { + type Output = Point; + + fn mul(self, rhs: Point) -> Self::Output { + Point(self.0.transform_point3a(rhs.0)) + } +} + pub struct Ray { origin: Point, direction: Vector, diff --git a/core/src/geometry/shapes.rs b/core/src/geometry/shapes.rs new file mode 100644 index 0000000..0a237b2 --- /dev/null +++ b/core/src/geometry/shapes.rs @@ -0,0 +1,90 @@ +use std::fmt; + +use enum_dispatch::enum_dispatch; + +use super::{Point, Ray, Vector}; + +#[derive(Debug)] +pub(crate) struct Hit { + point: Point, + normal: Vector, + t: f32, +} + +#[enum_dispatch] +pub(crate) trait Hittable { + fn intersect(&self, ray: Ray, t_max: f32) -> Option; +} + +#[derive(Debug)] +pub struct Sphere { + center: Point, + radius: f32, +} + +impl Hittable for Sphere { + fn intersect(&self, ray: Ray, t_max: f32) -> Option { + let oc = self.center - ray.origin; + let a = ray.direction.length_squared(); + let h = ray.direction.dot(oc); + let c = oc.length_squared() - self.radius * self.radius; + let d = h * h - a * c; + + (d > 0.0) + .then(|| (h - d.sqrt()) / a) + .filter(|t| *t < t_max && *t > 0.0) + .map(|t| { + let point = ray.at(t); + let normal = point - self.center; + Hit { point, normal, t } + }) + } +} + +#[derive(Debug)] +pub struct Plane { + normal: Vector, + d: f32, +} + +impl Hittable for Plane { + fn intersect(&self, ray: Ray, t_max: f32) -> Option { + let n_dot_r = self.normal.dot(ray.direction); + (n_dot_r.abs() > f32::EPSILON) + .then(|| { + let n_dot_o = self.normal.dot(ray.origin.to_vector()); + -(n_dot_o + self.d) / n_dot_r + }) + .filter(|t| *t < t_max && *t > 0.0) + .map(|t| Hit { + point: ray.at(t), + normal: self.normal, + t, + }) + } +} + +#[enum_dispatch(Hittable)] +pub enum Shape { + Sphere, + Plane, +} + +impl fmt::Debug for Shape { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Sphere(s) => s.fmt(f), + Self::Plane(p) => p.fmt(f), + } + } +} + +impl Shape { + pub fn sphere(center: Point, radius: f32) -> Shape { + Shape::from(Sphere { center, radius }) + } + + pub fn plane(normal: Vector, d: f32) -> Shape { + Shape::from(Plane { normal, d }) + } +} diff --git a/core/src/lib.rs b/core/src/lib.rs index fc99786..9c189dd 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -2,8 +2,11 @@ pub mod impl_bytemuck; pub mod color; + mod geometry; +pub use geometry::{Point, Transform, Vector, shapes::Shape}; + use color::Rgba; pub struct Scene; diff --git a/examples/basic.py b/examples/basic.py old mode 100644 new mode 100755 index c96e612..46dab78 --- a/examples/basic.py +++ b/examples/basic.py @@ -1,11 +1,10 @@ -from PIL import Image -from mechthild import Scene +#!python -WIDTH = 800 -HEIGHT = 600 +from mechthild import Shape -render_result = Scene().render(WIDTH, HEIGHT) -rgba = bytes([int(v * 255) for v in render_result]) +s1 = Shape.sphere((0.0, 0.5, 0.0), 0.5) +s2 = Shape.sphere((1.5, 2.0, -1.0), 2.0) -img = Image.frombuffer('RGBA', (WIDTH, HEIGHT), rgba) -img.save("test.png") +ground = Shape.plane((0.0, 1.0, 0.0), 0.0) + +print(f"{s1}\n{s2}\n{ground}") diff --git a/py/src/lib.rs b/py/src/lib.rs index abbe3aa..a428282 100644 --- a/py/src/lib.rs +++ b/py/src/lib.rs @@ -1,7 +1,27 @@ use bytemuck::allocation::cast_slice_box; -use mechthild_core::{Scene, color::Rgba}; +use mechthild_core::{Scene, Shape, color::Rgba}; use pyo3::prelude::*; +#[pyclass(name = "Shape")] +struct PyShape(Shape); + +#[pymethods] +impl PyShape { + #[staticmethod] + pub fn sphere(center: (f32, f32, f32), radius: f32) -> Self { + Self(Shape::sphere(center.into(), radius)) + } + + #[staticmethod] + pub fn plane(normal: (f32, f32, f32), d: f32) -> Self { + Self(Shape::plane(normal.into(), d)) + } + + pub fn __str__(&self) -> PyResult { + Ok(format!("{:?}", self.0)) + } +} + #[pyclass(name = "Scene")] struct PyScene(Scene); @@ -22,6 +42,7 @@ impl PyScene { /// A Python module implemented in Rust. #[pymodule] fn mechthild(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; m.add_class::()?; Ok(()) }