use crate::{NurbsCurve, NurbsSurface, Vec3, Vec4};
use super::reader::ParsedEntity;
use super::writer::{real_field, IgesEntity};
const WEIGHT_TOL: f64 = 1e-12;
pub(crate) fn curve_to_126(curve: &NurbsCurve) -> Result<IgesEntity, String> {
let num = curve.control_points.len();
if num < 2 {
return Err("iges: curve has fewer than 2 control points".into());
}
let degree = curve.degree;
let k = (num - 1) as i64;
let expected_knots = num + degree + 1;
if curve.knots.len() != expected_knots {
return Err(format!(
"iges: curve knot count {} != control points {} + degree {} + 1",
curve.knots.len(),
num,
degree
));
}
let rational = curve
.control_points
.iter()
.any(|c| (c.w - 1.0).abs() > WEIGHT_TOL);
let [t0, t1] = curve.domain()?;
let mut params: Vec<String> = Vec::new();
params.push(k.to_string()); params.push((degree as i64).to_string()); params.push("0".into()); params.push("0".into()); params.push(if rational { "0" } else { "1" }.into()); params.push("0".into()); for knot in &curve.knots {
params.push(real_field(*knot));
}
for c in &curve.control_points {
params.push(real_field(c.w));
}
for c in &curve.control_points {
let p = c.point()?;
params.push(real_field(p.x));
params.push(real_field(p.y));
params.push(real_field(p.z));
}
params.push(real_field(t0)); params.push(real_field(t1)); params.push(real_field(0.0)); params.push(real_field(0.0));
params.push(real_field(0.0));
Ok(IgesEntity::new(126, params))
}
fn flag(value: bool) -> String {
if value { "1" } else { "0" }.into()
}
fn closed_directions(surface: &NurbsSurface) -> Result<(bool, bool), String> {
let net = &surface.control_points;
let mut points = Vec::with_capacity(net.len() * net[0].len());
for row in net {
for c in row {
points.push(c.point()?);
}
}
let (mut lo, mut hi) = (points[0], points[0]);
for p in &points {
lo = Vec3::new(lo.x.min(p.x), lo.y.min(p.y), lo.z.min(p.z));
hi = Vec3::new(hi.x.max(p.x), hi.y.max(p.y), hi.z.max(p.z));
}
let tolerance = 1e-9 * (1.0 + hi.sub(lo).length());
let same = |a: &Vec4, b: &Vec4| -> Result<bool, String> {
Ok(a.point()?.sub(b.point()?).length() <= tolerance && (a.w - b.w).abs() <= WEIGHT_TOL)
};
let (last_u, last_v) = (net.len() - 1, net[0].len() - 1);
let mut closed_u = true;
for j in 0..=last_v {
closed_u &= same(&net[0][j], &net[last_u][j])?;
}
let mut closed_v = true;
for row in net {
closed_v &= same(&row[0], &row[last_v])?;
}
Ok((closed_u, closed_v))
}
pub(crate) fn surface_to_128(surface: &NurbsSurface) -> Result<IgesEntity, String> {
let num_u = surface.control_points.len();
if num_u < 2 {
return Err("iges: surface has fewer than 2 rows of control points".into());
}
let num_v = surface.control_points[0].len();
if num_v < 2 {
return Err("iges: surface has fewer than 2 columns of control points".into());
}
let (du, dv) = (surface.degree_u, surface.degree_v);
let k1 = (num_u - 1) as i64;
let k2 = (num_v - 1) as i64;
if surface.knots_u.len() != num_u + du + 1 {
return Err("iges: surface u-knot count inconsistent with control net".into());
}
if surface.knots_v.len() != num_v + dv + 1 {
return Err("iges: surface v-knot count inconsistent with control net".into());
}
let rational = surface
.control_points
.iter()
.flatten()
.any(|c| (c.w - 1.0).abs() > WEIGHT_TOL);
let [u0, u1] = surface.domain_u()?;
let [v0, v1] = surface.domain_v()?;
let mut params: Vec<String> = Vec::new();
params.push(k1.to_string());
params.push(k2.to_string());
params.push((du as i64).to_string());
params.push((dv as i64).to_string());
let (closed_u, closed_v) = closed_directions(surface)?;
params.push(flag(closed_u)); params.push(flag(closed_v)); params.push(if rational { "0" } else { "1" }.into()); params.push(flag(closed_u)); params.push(flag(closed_v)); for knot in &surface.knots_u {
params.push(real_field(*knot));
}
for knot in &surface.knots_v {
params.push(real_field(*knot));
}
for j in 0..num_v {
for i in 0..num_u {
params.push(real_field(surface.control_points[i][j].w));
}
}
for j in 0..num_v {
for i in 0..num_u {
let p = surface.control_points[i][j].point()?;
params.push(real_field(p.x));
params.push(real_field(p.y));
params.push(real_field(p.z));
}
}
params.push(real_field(u0));
params.push(real_field(u1));
params.push(real_field(v0));
params.push(real_field(v1));
Ok(IgesEntity::new(128, params))
}
pub(crate) fn surface_from_128(entity: &ParsedEntity, coord_scale: f64) -> Result<NurbsSurface, String> {
let k1 = entity.int(0)?;
let k2 = entity.int(1)?;
let du = entity.int(2)? as usize;
let dv = entity.int(3)? as usize;
if k1 < 1 || k2 < 1 {
return Err(format!("iges 128: invalid upper indices K1={k1} K2={k2}"));
}
let num_u = (k1 + 1) as usize;
let num_v = (k2 + 1) as usize;
let u_knot_count = num_u + du + 1;
let v_knot_count = num_v + dv + 1;
let mut idx = 9; let mut knots_u = Vec::with_capacity(u_knot_count);
for _ in 0..u_knot_count {
knots_u.push(entity.real(idx)?);
idx += 1;
}
let mut knots_v = Vec::with_capacity(v_knot_count);
for _ in 0..v_knot_count {
knots_v.push(entity.real(idx)?);
idx += 1;
}
let count = num_u * num_v;
let mut weights = Vec::with_capacity(count);
for _ in 0..count {
weights.push(entity.real(idx)?);
idx += 1;
}
let mut control_points = vec![vec![Vec4::from_point(Vec3::new(0.0, 0.0, 0.0), 1.0); num_v]; num_u];
let mut w_index = 0;
for j in 0..num_v {
for i in 0..num_u {
let x = entity.real(idx)? * coord_scale;
let y = entity.real(idx + 1)? * coord_scale;
let z = entity.real(idx + 2)? * coord_scale;
idx += 3;
control_points[i][j] = Vec4::from_point(Vec3::new(x, y, z), weights[w_index]);
w_index += 1;
}
}
NurbsSurface::new(du, dv, knots_u, knots_v, control_points)
}