use glam::{Vec2, Vec3};
#[inline(always)]
pub fn sdf_regular_polygon(p: Vec3, radius: f32, n_sides: f32, half_height: f32) -> f32 {
let n = n_sides.max(3.0).trunc();
let an = std::f32::consts::PI / n;
let (acs_s, acs_c) = alice_det_math::sin_cos(an);
let bn = (alice_det_math::atan2(p.x, p.z)).rem_euclid(2.0 * an) - an;
let r = alice_det_math::hypot(p.x, p.z);
let (bs, bc) = alice_det_math::sin_cos(bn);
let mut q = Vec2::new(r * bc, (r * bs).abs());
q -= Vec2::new(radius * acs_c, radius * acs_s);
q.y += super::clamp_total(-q.y, 0.0, radius * acs_s);
let d_2d = q.length() * q.x.signum();
let d_y = p.y.abs() - half_height;
let w = Vec2::new(d_2d.max(0.0), d_y.max(0.0));
d_2d.max(d_y).min(0.0) + w.length()
}
#[allow(
clippy::disallowed_methods,
reason = "test code: the platform libm and fused mul_add serve as independent references"
)]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_regular_polygon_center_inside() {
let d = sdf_regular_polygon(Vec3::ZERO, 1.0, 6.0, 0.5);
assert!(d < 0.0, "Center should be inside, got {}", d);
}
#[test]
fn test_regular_polygon_far_outside() {
let d = sdf_regular_polygon(Vec3::new(5.0, 0.0, 0.0), 1.0, 6.0, 0.5);
assert!(d > 0.0, "Far point should be outside, got {}", d);
}
#[test]
fn test_regular_polygon_bounded_in_every_sector() {
for (x, z) in [
(-3.0, -3.0),
(0.0, -3.0),
(3.0, -3.0),
(-3.0, 3.0),
(0.0, 3.0),
] {
let d = sdf_regular_polygon(Vec3::new(x, 0.0, z), 1.0, 6.0, 0.5);
assert!(d > 1.5, "({x}, {z}) must be far outside, got {d}");
}
}
#[test]
fn test_regular_polygon_vertex_and_apothem() {
let d_v = sdf_regular_polygon(Vec3::new(0.0, 0.0, 1.0), 1.0, 6.0, 0.5);
assert!(d_v.abs() < 1e-4, "vertex should be on surface, got {d_v}");
let ap = (std::f32::consts::PI / 6.0).cos();
let d_a = sdf_regular_polygon(
Vec3::new(
ap * (std::f32::consts::PI / 6.0).sin(),
0.0,
ap * (std::f32::consts::PI / 6.0).cos(),
),
1.0,
6.0,
0.5,
);
assert!(
d_a.abs() < 1e-4,
"apothem point should be on surface, got {d_a}"
);
}
#[test]
fn test_regular_polygon_symmetry_y() {
let d1 = sdf_regular_polygon(Vec3::new(0.2, 0.2, 0.3), 1.0, 6.0, 0.5);
let d2 = sdf_regular_polygon(Vec3::new(0.2, -0.2, 0.3), 1.0, 6.0, 0.5);
assert!((d1 - d2).abs() < 0.001, "Should be symmetric in Y");
}
#[test]
fn test_regular_polygon_symmetry_x() {
let d1 = sdf_regular_polygon(Vec3::new(0.3, 0.1, 0.2), 1.0, 6.0, 0.5);
let d2 = sdf_regular_polygon(Vec3::new(-0.3, 0.1, 0.2), 1.0, 6.0, 0.5);
assert!((d1 - d2).abs() < 0.001, "Should be symmetric in X");
}
}