use crate::alignment::AlignmentCurve;
use crate::router::GeometryProcessor;
use crate::{Mesh, Result};
use ifc_lite_core::{DecodedEntity, EntityDecoder, IfcSchema, IfcType};
use nalgebra::{Point3, Vector3};
use std::sync::OnceLock;
fn t_alignment_curve() -> IfcType {
static T: OnceLock<IfcType> = OnceLock::new();
*T.get_or_init(|| IfcType::from_str("IFCALIGNMENTCURVE"))
}
pub struct IfcAlignmentProcessor;
impl IfcAlignmentProcessor {
pub fn new() -> Self {
Self
}
}
impl Default for IfcAlignmentProcessor {
fn default() -> Self {
Self::new()
}
}
const SAMPLE_STEP_FILE_UNITS: f64 = 1.0;
const MAX_SAMPLES: usize = 5_000;
const RIBBON_HALF_WIDTH_FILE_UNITS: f64 = 0.25;
impl GeometryProcessor for IfcAlignmentProcessor {
fn process(
&self,
entity: &DecodedEntity,
decoder: &mut EntityDecoder,
_schema: &IfcSchema,
) -> Result<Mesh> {
let curve = locate_axis_curve(entity, decoder)?;
let alignment = match AlignmentCurve::parse(&curve, decoder)? {
Some(a) => a,
None => return Ok(Mesh::new()),
};
let length = alignment.horizontal_length();
if !(length.is_finite() && length > 0.0) {
return Ok(Mesh::new());
}
let raw_count = ((length / SAMPLE_STEP_FILE_UNITS).ceil() as usize).max(1);
let (sample_step, sample_count) = if raw_count > MAX_SAMPLES {
(length / MAX_SAMPLES as f64, MAX_SAMPLES + 1)
} else {
(SAMPLE_STEP_FILE_UNITS, raw_count + 1)
};
let mut left_pts: Vec<Point3<f64>> = Vec::with_capacity(sample_count);
let mut right_pts: Vec<Point3<f64>> = Vec::with_capacity(sample_count);
for i in 0..sample_count {
let station = (i as f64 * sample_step).min(length);
let frame = alignment.evaluate(station);
let offset = frame.right * RIBBON_HALF_WIDTH_FILE_UNITS;
left_pts.push(frame.origin - offset);
right_pts.push(frame.origin + offset);
}
let n = left_pts.len();
let mut mesh = Mesh::with_capacity(n * 2, (n - 1) * 6);
let up = Vector3::new(0.0, 0.0, 1.0);
for i in 0..n {
mesh.add_vertex(left_pts[i], up);
mesh.add_vertex(right_pts[i], up);
}
for i in 0..(n - 1) {
let a = (i * 2) as u32; let b = a + 1; let c = a + 2; let d = a + 3; mesh.add_triangle(a, b, d);
mesh.add_triangle(a, d, c);
}
Ok(mesh)
}
fn supported_types(&self) -> Vec<IfcType> {
vec![IfcType::IfcAlignment]
}
}
fn locate_axis_curve(
entity: &DecodedEntity,
decoder: &mut EntityDecoder,
) -> Result<DecodedEntity> {
for idx in [7usize, 8, 6] {
let Some(attr) = entity.get(idx) else { continue };
if attr.is_null() {
continue;
}
let Some(resolved) = decoder.resolve_ref(attr)? else {
continue;
};
if resolved.ifc_type == t_alignment_curve()
|| resolved.ifc_type == IfcType::IfcPolyline
{
return Ok(resolved);
}
}
Err(crate::Error::geometry(
"IfcAlignment missing recognisable Axis curve (expected IfcAlignmentCurve or IfcPolyline)"
.to_string(),
))
}