#![allow(dead_code)]
use std::f32::consts::PI;
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct CyclideParams {
pub a: f32,
pub c: f32,
pub b: f32,
pub u_segs: usize,
pub v_segs: usize,
}
impl CyclideParams {
#[allow(dead_code)]
pub fn new(a: f32, c: f32, u_segs: usize, v_segs: usize) -> Self {
let b = (a * a - c * c).abs().sqrt();
CyclideParams {
a,
c,
b,
u_segs,
v_segs,
}
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct CyclideMesh {
pub positions: Vec<[f32; 3]>,
pub indices: Vec<u32>,
}
#[allow(dead_code)]
pub fn cyclide_point(u: f32, v: f32, a: f32, b: f32, c: f32) -> [f32; 3] {
let (su, cu) = u.sin_cos();
let (sv, cv) = v.sin_cos();
let denom = a - c * cu * cv;
let denom = if denom.abs() < 1e-8 {
1e-8_f32.copysign(denom)
} else {
denom
};
[
(b * b * cu + a * b * cv - b * c) / denom,
b * su * (a - c * cv) / denom,
b * sv * (a * cu - c) / denom,
]
}
#[allow(dead_code)]
pub fn build_cyclide_mesh(params: &CyclideParams) -> CyclideMesh {
let nu = params.u_segs.max(4);
let nv = params.v_segs.max(4);
let mut positions = Vec::with_capacity((nu + 1) * (nv + 1));
for iu in 0..=nu {
let u = 2.0 * PI * iu as f32 / nu as f32;
for iv in 0..=nv {
let v = 2.0 * PI * iv as f32 / nv as f32;
positions.push(cyclide_point(u, v, params.a, params.b, params.c));
}
}
let mut indices = Vec::new();
for iu in 0..nu {
for iv in 0..nv {
let a = (iu * (nv + 1) + iv) as u32;
let b = (iu * (nv + 1) + iv + 1) as u32;
let c = ((iu + 1) * (nv + 1) + iv) as u32;
let d = ((iu + 1) * (nv + 1) + iv + 1) as u32;
indices.extend_from_slice(&[a, b, c, b, d, c]);
}
}
CyclideMesh { positions, indices }
}
#[allow(dead_code)]
pub fn cyclide_vertex_count(m: &CyclideMesh) -> usize {
m.positions.len()
}
#[allow(dead_code)]
pub fn cyclide_triangle_count(m: &CyclideMesh) -> usize {
m.indices.len() / 3
}
#[cfg(test)]
mod tests {
use super::*;
fn default_params() -> CyclideParams {
CyclideParams::new(3.0, 1.0, 16, 16)
}
#[test]
fn b_computed_correctly() {
let p = default_params();
assert!((p.b - (8.0_f32).sqrt()).abs() < 1e-4);
}
#[test]
fn vertex_count_correct() {
let p = default_params();
let m = build_cyclide_mesh(&p);
assert_eq!(m.positions.len(), (p.u_segs + 1) * (p.v_segs + 1));
}
#[test]
fn indices_multiple_of_three() {
let m = build_cyclide_mesh(&default_params());
assert_eq!(m.indices.len() % 3, 0);
}
#[test]
fn all_positions_finite() {
let m = build_cyclide_mesh(&default_params());
for p in &m.positions {
assert!(p[0].is_finite() && p[1].is_finite() && p[2].is_finite());
}
}
#[test]
fn triangle_count_correct() {
let p = default_params();
let m = build_cyclide_mesh(&p);
assert_eq!(cyclide_triangle_count(&m), p.u_segs * p.v_segs * 2);
}
#[test]
fn vertex_count_helper() {
let m = build_cyclide_mesh(&default_params());
assert_eq!(cyclide_vertex_count(&m), m.positions.len());
}
#[test]
fn cyclide_point_finite() {
let p = cyclide_point(1.0, 2.0, 3.0, (8.0_f32).sqrt(), 1.0);
assert!(p[0].is_finite() && p[1].is_finite() && p[2].is_finite());
}
#[test]
fn index_max_within_bounds() {
let m = build_cyclide_mesh(&default_params());
let max_idx = m.indices.iter().copied().max().unwrap_or(0) as usize;
assert!(max_idx < m.positions.len());
}
#[test]
fn more_segs_more_vertices() {
let p1 = CyclideParams::new(3.0, 1.0, 8, 8);
let p2 = CyclideParams::new(3.0, 1.0, 16, 16);
let m1 = build_cyclide_mesh(&p1);
let m2 = build_cyclide_mesh(&p2);
assert!(m2.positions.len() > m1.positions.len());
}
#[test]
fn min_segs_enforced() {
let p = CyclideParams {
a: 3.0,
b: (8.0_f32).sqrt(),
c: 1.0,
u_segs: 1,
v_segs: 1,
};
let m = build_cyclide_mesh(&p);
assert_eq!(m.positions.len(), 5 * 5);
}
}