use glam::Vec3;
use indicatrix_formats::asc::AscSchedule;
use super::{CutError, StandardGemCuts};
use crate::geometry::{brep::GemPolyhedron, plane::GpuFacetPlane};
impl StandardGemCuts {
#[must_use]
pub fn from_asc_schedule(schedule: &AscSchedule) -> Vec<GpuFacetPlane> {
let gear_teeth = (schedule.gear_teeth_abs().max(1)) as f32;
let gear_reference_angle = schedule.gear_reference_angle as f32;
let mut planes = Vec::with_capacity(schedule.facet_plane_count());
let mut last_side_is_crown = true;
for tier in &schedule.tiers {
let is_crown = if tier.angle_deg == 0.0 {
if tier.angle_deg.is_sign_negative() {
false
} else {
last_side_is_crown
}
} else {
tier.angle_deg > 0.0
};
last_side_is_crown = is_crown;
let theta = (tier.angle_deg.abs() as f32).to_radians();
let sin_theta = theta.sin();
let cos_theta = theta.cos();
let d = -(tier.mast.abs() as f32);
if tier.indices.is_empty() {
let n = if is_crown {
Vec3::new(0.0, cos_theta, sin_theta)
} else {
Vec3::new(0.0, -cos_theta, sin_theta)
};
planes.push(GpuFacetPlane::new(n, d));
continue;
}
for &idx in &tier.indices {
let phi = Self::index_to_azimuth(idx as f32, gear_teeth, gear_reference_angle);
let (sin_phi, cos_phi) = (phi.sin(), phi.cos());
let n = if is_crown {
Vec3::new(sin_theta * cos_phi, cos_theta, sin_theta * sin_phi)
} else {
Vec3::new(sin_theta * cos_phi, -cos_theta, sin_theta * sin_phi)
};
planes.push(GpuFacetPlane::new(n, d));
}
}
dedup_planes(planes)
}
pub fn reconstruct_validated_brep_from_asc(
schedule: &AscSchedule,
) -> Result<GemPolyhedron, CutError> {
let planes = Self::from_asc_schedule(schedule);
let plane_count = planes.len();
let hull = GemPolyhedron::from_planes(planes)?;
let untouched = hull.untouched_planes();
if untouched.is_empty() {
Ok(hull)
} else {
Err(CutError::OverConstrained {
untouched_count: untouched.len(),
plane_count,
untouched,
})
}
}
}
pub(super) fn dedup_planes(planes: Vec<GpuFacetPlane>) -> Vec<GpuFacetPlane> {
const QUANT: f32 = 1.0 / 2048.0;
const OFFSET_EPSILON: f32 = QUANT;
const NORMAL_DOT_EPSILON: f32 = 1.0 - 1e-5;
let mut kept: Vec<GpuFacetPlane> = Vec::with_capacity(planes.len());
for plane in planes {
let duplicate_of = kept.iter().position(|k| {
let dot = k.normal[0].mul_add(
plane.normal[0],
k.normal[1].mul_add(plane.normal[1], k.normal[2] * plane.normal[2]),
);
dot >= NORMAL_DOT_EPSILON && (k.d - plane.d).abs() <= OFFSET_EPSILON
});
match duplicate_of {
Some(idx) if plane.d.abs() < kept[idx].d.abs() => kept[idx] = plane,
Some(_) => {}
None => kept.push(plane),
}
}
kept
}