mechthild/core/src/lib.rs
2025-05-28 13:36:19 -04:00

76 lines
2 KiB
Rust

#[cfg(feature = "bytemuck")]
pub mod impl_bytemuck;
pub mod color;
mod camera;
mod geometry;
mod sampling;
use core::f32;
pub use camera::Camera;
pub use geometry::{Point, Transform, Vector, shapes::Shape};
use color::Rgba;
use geometry::{
Ray,
shapes::{Hit, Hittable},
};
use sampling::Sampler;
pub struct Scene {
camera: Camera,
contents: Vec<Shape>,
}
impl Scene {
pub fn new(camera: Camera, contents: Vec<Shape>) -> Self {
Self { camera, contents }
}
pub fn render(&self, width: u32, height: u32, samples: u32) -> Box<[Rgba]> {
let mut sampler = Sampler::new(width, height, samples, 0);
let mut data = Box::new_uninit_slice(width as usize * height as usize);
let w = width as f32;
let h = height as f32;
let aspect_ratio = h / w;
for (i, pixel) in data.iter_mut().enumerate() {
let bx = i as u32 % width;
let by = i as u32 / width;
let mut rgb = Vector::ZERO;
for mut pixel_samples in sampler.pixel(bx, by) {
let (x_offset, y_offset) = pixel_samples.get_2d();
let x = bx as f32 + x_offset;
let y = by as f32 + y_offset;
let cx = 2.0 * x / w - 1.0;
let cy = (1.0 - 2.0 * y / h) * aspect_ratio;
if let Some(h) = self.intersect(self.camera.ray(cx, cy)) {
rgb += h.normal.normalize() * 0.5 + Vector::new(0.5, 0.5, 0.5);
}
}
rgb /= samples as f32;
pixel.write(Rgba::new(rgb.x, rgb.y, rgb.z, 1.0));
}
unsafe { data.assume_init() }
}
fn intersect(&self, ray: Ray) -> Option<Hit> {
let mut hit: Option<Hit> = None;
for shape in &self.contents {
let t_max = hit.as_ref().map(|h| h.t).unwrap_or(f32::INFINITY);
if let Some(h) = shape.intersect(&ray, t_max) {
hit = Some(h);
}
}
hit
}
}