more geometry stuff, shapes

This commit is contained in:
wires 2025-04-23 12:16:56 -04:00
parent 48abb19464
commit 38e28a2d57
Signed by: wires
SSH key fingerprint: SHA256:9GtP+M3O2IivPDlw1UY872UPUuJH2gI0yG6ExBxaaiM
7 changed files with 190 additions and 13 deletions

13
Cargo.lock generated
View file

@ -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",
]

View file

@ -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]

View file

@ -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<Vector> 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<Point> 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<Vector> 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<Vector> for Transform {
}
}
impl Mul<Point> 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,

View file

@ -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<Hit>;
}
#[derive(Debug)]
pub struct Sphere {
center: Point,
radius: f32,
}
impl Hittable for Sphere {
fn intersect(&self, ray: Ray, t_max: f32) -> Option<Hit> {
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<Hit> {
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 })
}
}

View file

@ -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;

15
examples/basic.py Normal file → Executable file
View file

@ -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}")

View file

@ -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<String> {
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::<PyShape>()?;
m.add_class::<PyScene>()?;
Ok(())
}