diff --git a/src/lib.rs b/src/lib.rs index 68a6b75..d1617eb 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -3,6 +3,7 @@ use std::error::Error; use glam::{Mat4, UVec2, Vec3, Vec4, vec3, vec4}; mod camera; +mod profile; mod render; const BACKGROUND: Vec4 = vec4(0.00, 0.39, 0.52, 1.00); diff --git a/src/profile.rs b/src/profile.rs new file mode 100644 index 0000000..912fbec --- /dev/null +++ b/src/profile.rs @@ -0,0 +1,98 @@ +use std::f32::consts::PI; + +use glam::FloatExt; + +#[derive(Debug, Clone, Copy)] +pub struct Profile { + pub addendum: f32, + pub dedendum: f32, + pub pressure_angle: f32, +} + +impl Default for Profile { + fn default() -> Self { + Profile { + addendum: 1.0, + dedendum: 1.25, + pressure_angle: const { 20.0f32.to_radians() }, + } + } +} + +impl Profile { + pub(crate) const PITCH: f32 = PI; + + pub fn y_from_x(&self, x: f32) -> f32 { + self.y_from_tooth(x / Self::PITCH) + } + + pub fn y_from_tooth(&self, t: f32) -> f32 { + debug_assert!(self.addendum > 0.0); + debug_assert!(self.dedendum > 0.0); + debug_assert!(self.pressure_angle > 0.0); + let t = t.fract_gl(); + let slope = PI * self.slope(); + let a = self.addendum / slope; + let d = self.dedendum / slope; + debug_assert!(2.0 * a + 2.0 * d < 1.0, "pressure angle is too high"); + if t < a { + slope * t + } else if t < 0.5 - a { + self.addendum + } else if t < 0.5 + d { + -(t - 0.5) * slope + } else if t < 1.0 - d { + -self.dedendum + } else { + (t - 1.0) * slope + } + } + + pub fn slope(&self) -> f32 { + 1.0 / self.pressure_angle.tan() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_profile() { + let profile = Profile::default(); + let eps = 0.01; + for (t, y) in [ + (0.0, 0.0), + (0.25, profile.addendum), + (0.5, 0.0), + (0.75, -profile.dedendum), + (1.0, 0.0), + (1.25, profile.addendum), + ] { + let x = Profile::PITCH * t; + let comp_y = profile.y_from_x(x); + assert!( + (comp_y - y).abs() < eps, + "At t={t} (x={x}), expected y={y}, got y={comp_y}" + ); + } + for (t, slope) in [ + (0.0, 1.0), + (eps, 1.0), + (0.25, 0.0), + (0.5, -1.0), + (0.75, 0.0), + (1.0 - eps, 1.0), + (1.0, 1.0), + (1.0 + eps, 1.0), + ] { + let x = Profile::PITCH * t; + let comp_slope = (profile.y_from_x(x + eps) - profile.y_from_x(x - eps)) / (2.0 * eps); + let exp_slope = profile.slope() * slope; + assert!( + (comp_slope - exp_slope).abs() < eps, + "At t={t} (x={x}), expected slope={exp_slope}, got slope={comp_slope}" + ); + } + } +}