From 3a49460fe6aae3d34af5a843561c415509c9fa4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20R=C3=B6ger?= Date: Thu, 2 Apr 2026 15:10:09 +0200 Subject: [PATCH] feat(sphere): add texture sphere --- scenes/demo-3.lisp | 47 ++++++-------- src/raytracer/lisp.rs | 21 +++++++ src/raytracer/sphere.rs | 134 ++++++++++++++++++++++++++++------------ 3 files changed, 135 insertions(+), 67 deletions(-) diff --git a/scenes/demo-3.lisp b/scenes/demo-3.lisp index 3f9b06a..f41fb98 100644 --- a/scenes/demo-3.lisp +++ b/scenes/demo-3.lisp @@ -17,32 +17,7 @@ (color 0 0.6 0) 50 0.25)) -(set 's1 - (sphere - (point 0 1 0) 1 blue)) -(set 's2 - (sphere - (point 2 0.5 2) 0.5 green)) - -(defun spiral-sphere (i n) - (sphere - (progn - (print "Spiral Sphere at: ") - (println (point - (* 2 (cos (/ (* i 6.2) n))) - 0.5 - (* 2 (sin (/ (* i 6.2) n))))) - ) - 0.5 red)) - -(defun spiral (scn i n) - (if (< i n) - (scene-add - (spiral scn (+ i 1) n) - (spiral-sphere i n)) - scn)) - -(set 'mandelbrot +(set 'mandelbrot-red (mandelbrot-texture 1800.0 (point2 -0.7489967346191402 -0.06952285766601607) @@ -52,9 +27,27 @@ (color 0.3 0 0) )) +(set 'mandelbrot-blue + (mandelbrot-texture + 1800.0 + (point2 -0.7489967346191402 -0.06952285766601607) + 1000 + (color 0 0 0.3) + (color 0 0 0.3) + (color 0 0 0.3) + )) + +(set 's1 + (sphere + (point 0 1 0) 1 blue)) +(set 's2 + (sphere + (point 2 0.5 2) 0.5 green)) + + (set 'p1 (texture-plane - mandelbrot + mandelbrot-red (point 0 0 0) (vector 0 1 0) 1.0 diff --git a/src/raytracer/lisp.rs b/src/raytracer/lisp.rs index a14bbae..078d4cc 100644 --- a/src/raytracer/lisp.rs +++ b/src/raytracer/lisp.rs @@ -2,6 +2,7 @@ use std::path::PathBuf; use crate::raytracer::{ scene::Scene, + sphere::TextureSphere, texture::TextureWrapper, types::{Light, Point2}, }; @@ -74,6 +75,22 @@ pub fn sphere( Ok(ForeignDataWrapper::new(RTObjectWrapper::from(Sphere::new(*pos, rad, *mat))).into()) } +#[native_lisp_function(eval)] +pub fn texture_sphere( + pos: ForeignDataWrapper, + rad: f64, + tex: ForeignDataWrapper, +) -> Result, EvalError> { + Ok( + ForeignDataWrapper::new(RTObjectWrapper::from(TextureSphere::new( + *pos, + rad, + tex.clone(), + ))) + .into(), + ) +} + #[native_lisp_function(eval)] pub fn plane( pos: ForeignDataWrapper, @@ -539,6 +556,10 @@ pub fn mk_raytrace(layer: &mut EnvironmentLayer) { Expression::Function(mandelbrot_texture), ); layer.set("sphere".to_string(), Expression::Function(sphere)); + layer.set( + "texture-sphere".to_string(), + Expression::Function(texture_sphere), + ); layer.set("scene".to_string(), Expression::Function(scene)); layer.set("scene-add".to_string(), Expression::Function(scene_add)); layer.set("camera".to_string(), Expression::Function(camera)); diff --git a/src/raytracer/sphere.rs b/src/raytracer/sphere.rs index fadb9f7..c3aab38 100644 --- a/src/raytracer/sphere.rs +++ b/src/raytracer/sphere.rs @@ -1,4 +1,7 @@ -use super::types::{Intersect, Material, Point3, Ray, Scalar, Vector3}; +use super::{ + texture::TextureWrapper, + types::{Intersect, Material, Point2, Point3, Ray, Scalar, Vector3}, +}; extern crate nalgebra as na; @@ -13,6 +16,17 @@ pub struct Sphere { material: Material, } +/// A sphere in 3D space +#[derive(PartialEq, Clone, Debug)] +pub struct TextureSphere { + /// Center of the sphere + center: Point3, + /// Radius of the sphere + radius: Scalar, + /// texture of the sphere + texture: TextureWrapper, +} + impl Sphere { /// Create a new sphere at `center` with `radius` and `material`. pub fn new(center: Point3, radius: Scalar, material: Material) -> Sphere { @@ -27,50 +41,47 @@ impl Sphere { /// Numerical error tolerance const EPSILON: Scalar = 1e-5; -impl Intersect for Sphere { - fn intersect(&self, ray: &Ray) -> Option<(Point3, Vector3, Scalar, Material)> { - let co = ray.origin - self.center; +fn intersect(ray: &Ray, center: &Point3, radius: Scalar) -> Option<(Point3, Vector3, Scalar)> { + let co = ray.origin - center; - let a = ray.direction.dot(&ray.direction); - let b = 2.0 * ray.direction.dot(&co); - let c = co.dot(&co) - (self.radius * self.radius); - let d = b * b - 4.0 * a * c; + let a = ray.direction.dot(&ray.direction); + let b = 2.0 * ray.direction.dot(&co); + let c = co.dot(&co) - (radius * radius); + let d = b * b - 4.0 * a * c; - if d >= 0.0 { - let e = d.sqrt(); - let t1 = (-b - e) / (2.0 * a); - let t2 = (-b + e) / (2.0 * a); - let mut t = Scalar::MAX; + if d >= 0.0 { + let e = d.sqrt(); + let t1 = (-b - e) / (2.0 * a); + let t2 = (-b + e) / (2.0 * a); + let mut t = Scalar::MAX; - if t1 > EPSILON && t1 < t { - t = t1; - } - if t2 > EPSILON && t2 < t { - t = t2; - } - - if t < Scalar::MAX { - let isect_pt: Point3 = ray.origin + ray.direction * t; - - if c >= 0.0 { - return Some(( - isect_pt, - (isect_pt - self.center) / self.radius, - t, - self.material.clone(), - )); - } else { - return Some(( - isect_pt, - -(isect_pt - self.center) / self.radius, - t, - self.material.clone(), - )); - } - } + if t1 > EPSILON && t1 < t { + t = t1; + } + if t2 > EPSILON && t2 < t { + t = t2; } - None + if t < Scalar::MAX { + let isect_pt: Point3 = ray.origin + ray.direction * t; + + if c >= 0.0 { + return Some((isect_pt, (isect_pt - center) / radius, t)); + } else { + return Some((isect_pt, -(isect_pt - center) / radius, t)); + } + } + } + + None +} + +impl Intersect for Sphere { + fn intersect(&self, ray: &Ray) -> Option<(Point3, Vector3, Scalar, Material)> { + match intersect(ray, &self.center, self.radius) { + Some((isect_pt, normal, t)) => Some((isect_pt, normal, t, self.material.clone())), + None => None, + } } } @@ -89,3 +100,46 @@ impl PartialOrd for Sphere { None } } + +impl TextureSphere { + /// Create a new sphere at `center` with `radius` and `texture`. + pub fn new(center: Point3, radius: Scalar, texture: TextureWrapper) -> TextureSphere { + TextureSphere { + center, + radius, + texture, + } + } +} + +impl Intersect for TextureSphere { + fn intersect(&self, ray: &Ray) -> Option<(Point3, Vector3, Scalar, Material)> { + match intersect(ray, &self.center, self.radius) { + Some((isect_pt, normal, t)) => { + let n_isect_pt = (isect_pt - self.center) / self.radius; + let uv: Point2 = Point2::new( + 0.5 + (n_isect_pt.z.atan2(n_isect_pt.x) / (2.0 * std::f64::consts::PI)), + 0.5 - (n_isect_pt.y).asin() / std::f64::consts::PI, + ); + Some((isect_pt, normal, t, self.texture.material_at(uv))) + } + None => None, + } + } +} + +impl std::fmt::Display for TextureSphere { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "(sphere center: {}, radius: {}, texture: {})", + self.center, self.radius, self.texture + ) + } +} + +impl PartialOrd for TextureSphere { + fn partial_cmp(&self, _other: &Self) -> Option { + None + } +}