use axiolid_contracts::{GeomError, GeomResult};
use axiolid_core::{Point3, Scalar};
use axiolid_curve::Polyline3;
fn spans(count: usize, closed: bool) -> Vec<(usize, usize)> {
if closed {
(0..count).map(|i| (i, (i + 1) % count)).collect()
} else {
(0..count.saturating_sub(1)).map(|i| (i, i + 1)).collect()
}
}
pub fn polyline_length(polyline: &Polyline3) -> GeomResult<Scalar> {
let mut total = 0.0;
for (i, j) in spans(polyline.points.len(), polyline.closed) {
total += segment_length(polyline.points[i], polyline.points[j])?;
}
Ok(total)
}
fn segment_length(a: Point3, b: Point3) -> GeomResult<Scalar> {
let length = (b - a).length();
if !length.is_finite() {
return Err(GeomError::Degenerate(
"polyline segment has a non-finite length".to_string(),
));
}
if length <= 0.0 {
return Err(GeomError::Degenerate(
"polyline has a zero-length segment, so distance along it is ambiguous".to_string(),
));
}
Ok(length)
}
pub fn polyline_parameter(polyline: &Polyline3, distance: Scalar) -> GeomResult<Scalar> {
let spans = spans(polyline.points.len(), polyline.closed);
if spans.is_empty() {
return Err(GeomError::Degenerate(format!(
"polyline with {} points has no evaluable segment",
polyline.points.len()
)));
}
let mut lengths = Vec::with_capacity(spans.len());
for (i, j) in &spans {
lengths.push(segment_length(polyline.points[*i], polyline.points[*j])?);
}
let total: Scalar = lengths.iter().sum();
if !distance.is_finite() || distance < 0.0 || distance > total {
return Err(GeomError::InvalidInput(format!(
"distance {distance} is outside the polyline's length [0, {total}]"
)));
}
let mut run = 0.0;
for (index, length) in lengths.iter().enumerate() {
if distance <= run + length {
let local = (distance - run) / length;
return Ok(index as Scalar + local);
}
run += length;
}
Ok(spans.len() as Scalar)
}