use crate::{scale_segments, Point3, TessellationQuality};
use ifc_lite_core::{DecodedEntity, EntityDecoder};
use super::bspline::{evaluate_bspline_curve, expand_knots};
pub(super) fn extract_vertex_coords(vertex: &DecodedEntity, decoder: &mut EntityDecoder) -> Option<Point3<f64>> {
let point_attr = vertex.get(0)?;
let point = decoder.resolve_ref(point_attr).ok().flatten()?;
let coords = point.get(0).and_then(|v| v.as_list())?;
let x = coords.first().and_then(|v| v.as_float()).unwrap_or(0.0);
let y = coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
let z = coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0);
Some(Point3::new(x, y, z))
}
pub(super) fn parse_curve_knots(curve: &DecodedEntity) -> Option<(Vec<i64>, Vec<f64>)> {
for (mi, ki) in [(5usize, 6usize), (6, 7)] {
let mults: Vec<i64> = match curve.get(mi).and_then(|a| a.as_list()) {
Some(l) => l.iter().filter_map(|v| v.as_int()).collect(),
None => continue,
};
let knots: Vec<f64> = match curve.get(ki).and_then(|a| a.as_list()) {
Some(l) => l.iter().filter_map(|v| v.as_float()).collect(),
None => continue,
};
if !mults.is_empty() && mults.len() == knots.len() {
return Some((mults, knots));
}
}
None
}
pub(super) fn read_trim_parameter(attr: &ifc_lite_core::AttributeValue) -> Option<f64> {
let list = attr.as_list()?;
let mut param: Option<f64> = None;
for item in list {
if let Some(inner) = item.as_list() {
if let Some(type_name) = inner.first().and_then(|v| v.as_string()) {
if type_name == "IFCPARAMETERVALUE" {
param = inner.get(1).and_then(|v| v.as_float());
continue;
}
}
}
if item.as_entity_ref().is_some() {
continue;
}
if let Some(f) = item.as_float() {
param = Some(f);
}
}
param
}
pub(super) fn sample_bspline_edge_curve(
curve: &DecodedEntity,
start: &Point3<f64>,
curve_forward: bool,
decoder: &mut EntityDecoder,
quality: TessellationQuality,
) -> Vec<Point3<f64>> {
sample_bspline_edge_curve_range(curve, start, curve_forward, None, decoder, quality)
}
pub(super) fn sample_bspline_edge_curve_range(
curve: &DecodedEntity,
start: &Point3<f64>,
curve_forward: bool,
trim_range: Option<(f64, f64)>,
decoder: &mut EntityDecoder,
quality: TessellationQuality,
) -> Vec<Point3<f64>> {
let degree = curve.get_float(0).unwrap_or(3.0) as usize;
let cp_list = match curve.get(1).and_then(|a| a.as_list()) {
Some(list) => list,
None => return vec![*start],
};
let control_points: Vec<Point3<f64>> = cp_list
.iter()
.filter_map(|ref_val| {
let id = ref_val.as_entity_ref()?;
let pt = decoder.decode_by_id(id).ok()?;
let coords = pt.get(0)?.as_list()?;
let x = coords.first()?.as_float().unwrap_or(0.0);
let y = coords.get(1).and_then(|v| v.as_float()).unwrap_or(0.0);
let z = coords.get(2).and_then(|v| v.as_float()).unwrap_or(0.0);
Some(Point3::new(x, y, z))
})
.collect();
if control_points.len() <= degree {
return vec![*start];
}
let Some((mults, knot_values)) = parse_curve_knots(curve) else {
return vec![*start];
};
let knots = expand_knots(&knot_values, &mults);
if knots.len() <= degree {
return vec![*start];
}
let t_min = knots[degree];
let t_max = knots[knots.len() - degree - 1];
let (t_lo, t_hi) = match trim_range {
Some((a, b)) => {
let lo = a.min(b).max(t_min);
let hi = a.max(b).min(t_max);
if hi - lo > 1e-12 { (lo, hi) } else { (t_min, t_max) }
}
None => (t_min, t_max),
};
let n_segments = scale_segments(control_points.len() * 2, 4, 16, quality);
let mut points = Vec::with_capacity(n_segments + 1);
points.push(*start);
for i in 1..n_segments {
let frac = i as f64 / n_segments as f64;
let t = if curve_forward {
t_lo + (t_hi - t_lo) * frac
} else {
t_hi - (t_hi - t_lo) * frac
};
let t_clamped = t.min(t_hi - 1e-6).max(t_lo);
let pt = evaluate_bspline_curve(t_clamped, degree, &control_points, &knots);
if let Some(prev) = points.last() {
let dist_sq = (pt.x - prev.x).powi(2) + (pt.y - prev.y).powi(2) + (pt.z - prev.z).powi(2);
if dist_sq < 1e-12 {
continue;
}
}
points.push(pt);
}
points
}