#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ScrewParams {
pub angle: f32,
pub pitch: f32,
pub steps: usize,
pub axis: usize,
}
impl Default for ScrewParams {
fn default() -> Self {
Self { angle: std::f32::consts::TAU, pitch: 0.0, steps: 16, axis: 1 }
}
}
#[derive(Debug, Clone, Default)]
pub struct ScrewResult {
pub positions: Vec<[f32; 3]>,
pub indices: Vec<u32>,
}
pub fn screw_profile(profile: &[[f32; 3]], params: &ScrewParams) -> ScrewResult {
let n = profile.len();
if n == 0 || params.steps == 0 {
return ScrewResult::default();
}
let steps = params.steps;
let mut out_pos: Vec<[f32; 3]> = Vec::with_capacity(n * (steps + 1));
let mut out_idx: Vec<u32> = Vec::new();
for s in 0..=steps {
let frac = s as f32 / steps as f32;
let theta = params.angle * frac;
let height = params.pitch * frac;
let cos_t = theta.cos();
let sin_t = theta.sin();
for &p in profile {
let rotated = rotate_around_axis(p, params.axis, cos_t, sin_t, height);
out_pos.push(rotated);
}
}
for s in 0..steps {
let ring0 = (s * n) as u32;
let ring1 = ((s + 1) * n) as u32;
for k in 0..(n.saturating_sub(1)) {
let a = ring0 + k as u32;
let b = ring0 + k as u32 + 1;
let c = ring1 + k as u32 + 1;
let d = ring1 + k as u32;
out_idx.extend_from_slice(&[a, b, c]);
out_idx.extend_from_slice(&[a, c, d]);
}
}
ScrewResult { positions: out_pos, indices: out_idx }
}
pub fn default_screw_params() -> ScrewParams {
ScrewParams::default()
}
pub fn screw_vertex_count(r: &ScrewResult) -> usize {
r.positions.len()
}
pub fn screw_triangle_count(r: &ScrewResult) -> usize {
r.indices.len() / 3
}
pub fn screw_height_extent(r: &ScrewResult) -> f32 {
if r.positions.is_empty() { return 0.0; }
let min_y: f32 = r.positions.iter().map(|p| p[1]).fold(f32::MAX, f32::min);
let max_y: f32 = r.positions.iter().map(|p| p[1]).fold(f32::MIN, f32::max);
max_y - min_y
}
fn rotate_around_axis(p: [f32; 3], axis: usize, cos_t: f32, sin_t: f32, height: f32) -> [f32; 3] {
match axis % 3 {
0 => {
[p[0] + height, p[1] * cos_t - p[2] * sin_t, p[1] * sin_t + p[2] * cos_t]
}
1 => {
[p[0] * cos_t + p[2] * sin_t, p[1] + height, -p[0] * sin_t + p[2] * cos_t]
}
_ => {
[p[0] * cos_t - p[1] * sin_t, p[0] * sin_t + p[1] * cos_t, p[2] + height]
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn simple_profile() -> Vec<[f32; 3]> {
vec![[1.0, 0.0, 0.0], [1.0, 1.0, 0.0], [1.0, 2.0, 0.0]]
}
#[test]
fn test_screw_vertex_count() {
let p = simple_profile();
let params = ScrewParams { steps: 8, ..ScrewParams::default() };
let r = screw_profile(&p, ¶ms);
assert_eq!(screw_vertex_count(&r), 3 * 9);
}
#[test]
fn test_screw_triangle_count_positive() {
let p = simple_profile();
let r = screw_profile(&p, &default_screw_params());
assert!(screw_triangle_count(&r) > 0);
}
#[test]
fn test_screw_empty_profile() {
let r = screw_profile(&[], &default_screw_params());
assert_eq!(screw_vertex_count(&r), 0);
}
#[test]
fn test_screw_zero_steps() {
let p = simple_profile();
let params = ScrewParams { steps: 0, ..ScrewParams::default() };
let r = screw_profile(&p, ¶ms);
assert_eq!(screw_vertex_count(&r), 0);
}
#[test]
fn test_screw_with_pitch_increases_height() {
let p = simple_profile();
let no_pitch = ScrewParams { pitch: 0.0, ..ScrewParams::default() };
let with_pitch = ScrewParams { pitch: 2.0, ..ScrewParams::default() };
let r0 = screw_profile(&p, &no_pitch);
let r1 = screw_profile(&p, &with_pitch);
assert!(screw_height_extent(&r1) > screw_height_extent(&r0));
}
#[test]
fn test_screw_default_params() {
let dp = default_screw_params();
assert!(dp.steps > 0);
assert!((dp.angle - std::f32::consts::TAU).abs() < 1e-5);
}
#[test]
fn test_rotate_around_y_identity_at_zero() {
let p = [2.0, 0.0, 0.0];
let r = rotate_around_axis(p, 1, 1.0, 0.0, 0.0);
assert!((r[0] - 2.0).abs() < 1e-5);
assert!(r[1].abs() < 1e-5);
}
#[test]
fn test_screw_axis_z() {
let p = simple_profile();
let params = ScrewParams { axis: 2, steps: 4, ..ScrewParams::default() };
let r = screw_profile(&p, ¶ms);
assert!(screw_vertex_count(&r) > 0);
}
#[test]
fn test_height_extent_no_pitch_near_zero() {
let p = vec![[1.0, 0.0, 0.0]];
let params = ScrewParams { pitch: 0.0, steps: 32, angle: std::f32::consts::TAU, axis: 1 };
let r = screw_profile(&p, ¶ms);
assert!(screw_height_extent(&r) < 1e-4);
}
}