From 7b0c09c3d7fd46f969c9fac2fbdb6941de6ced66 Mon Sep 17 00:00:00 2001 From: numzero Date: Sat, 16 Nov 2024 23:52:18 +0300 Subject: [PATCH] Add custom cylinder --- src/shape/cylinder.rs | 78 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/src/shape/cylinder.rs b/src/shape/cylinder.rs index baf21a9..0f06a21 100644 --- a/src/shape/cylinder.rs +++ b/src/shape/cylinder.rs @@ -2,6 +2,67 @@ use glam::{Vec3, Vec3Swizzles as _}; use crate::types::Ray; +pub struct Cylinder { + pub center: Vec3, + pub semiaxis: Vec3, + pub radius: f32, +} + +impl Cylinder { + /// Split a vector into a component along the axis and one orthogonal to it. + fn split(&self, dir: Vec3) -> (f32, Vec3) { + let along = dir.dot(self.semiaxis) / self.semiaxis.length_squared(); + (along, dir - along * self.semiaxis) + } + + fn cap_inout(p: f32, d: f32) -> (f32, f32) { + ((-d.signum() - p) / d.abs(), (d.signum() - p) / d.abs()) + } + + pub fn is_inside(&self, pt: Vec3) -> bool { + let (along, ortho) = self.split(pt - self.center); + along.abs() < 1. && ortho.length_squared() < self.radius.powi(2) + } + + fn trace_inout(&self, ray: Ray) -> Option<(f32, f32)> { + let (pos_along, pos_ortho) = self.split(ray.pos - self.center); + let (dir_along, dir_ortho) = self.split(ray.dir); + let (t_cap_in, t_cap_out) = Self::cap_inout(pos_along, dir_along); + if dir_ortho.length_squared() < 1e-3 { + if pos_ortho.length_squared() >= self.radius.powi(2) { + return None; + } + return Some((t_cap_in, t_cap_out)); + } + let (t_side_in, t_side_out) = solve_quadratic( + dir_ortho.length_squared(), + pos_ortho.dot(dir_ortho), + pos_ortho.length_squared() - self.radius.powi(2), + )?; + let t_in = f32::max(t_cap_in, t_side_in); + let t_out = f32::min(t_cap_out, t_side_out); + if t_out <= t_in { + return None; + } + Some((t_in, t_out)) + } + + pub fn trace_into(&self, ray: Ray) -> Option { + let (t, _) = self.trace_inout(ray)?; + if t < 0. { + return None; + } + Some(t) + } + + pub fn trace_out_of(&self, ray: Ray) -> Option { + let (_, t) = self + .trace_inout(ray) + .expect("the ray starts inside so *has* to cross the boundary"); + Some(t) + } +} + /// Цилиндр с центром в начале координат и осью вдоль оси Y. pub struct YCylinder { pub half_length: f32, @@ -88,6 +149,23 @@ mod tests { use crate::types::ray; use approx::assert_abs_diff_eq; use glam::vec3; + use rand::{Rng, SeedableRng}; + + #[test] + fn test_split() { + let mut rng = rand_pcg::Pcg64Mcg::seed_from_u64(17); + let cyl = Cylinder { + center: vec3(1., 2., 3.), + semiaxis: vec3(4., 5., 6.), + radius: 7., + }; + for _ in 0..100 { + let dir = vec3(rng.gen(), rng.gen(), rng.gen()); + let (along, ortho) = cyl.split(dir); + assert_abs_diff_eq!(along * cyl.semiaxis + ortho, dir, epsilon = 1e-5); + assert_abs_diff_eq!(cyl.semiaxis.dot(ortho), 0., epsilon = 1e-5); + } + } #[test] fn test_cylinder() {