use crate::nurbs::reader::{
is_periodic, marker_at, marker_positions, owned_marker_positions, read_control_points,
read_knots, take_tagged_int, KnotLayout, INT_WIDTHS, LEN_TO_MM,
};
use crate::nurbs::subtypes::{decode_cache_resolving_refs, SubtypeTables};
use crate::nurbs::toks;
use crate::nurbs::toks::Cur;
use crate::sab::Token;
use cadmpeg_ir::geometry::{NurbsCurve, NurbsSurface};
use cadmpeg_ir::math::Point3;
use crate::nurbs::toks::take_knot_table as knots;
fn control_points(
cur: &mut Cur<'_>,
count: usize,
cp_dims: usize,
) -> Option<(Vec<Point3>, Option<Vec<f64>>)> {
let mut points = Vec::new();
let mut weights = (cp_dims == 4).then(Vec::new);
for _ in 0..count {
let mut comps = [0.0f64; 4];
for comp in comps.iter_mut().take(cp_dims) {
*comp = cur.take_f64()?;
}
points.push(Point3::new(
comps[0] * LEN_TO_MM,
comps[1] * LEN_TO_MM,
comps[2] * LEN_TO_MM,
));
if let Some(w) = weights.as_mut() {
w.push(comps[3]);
}
}
Some((points, weights))
}
pub(crate) fn surface_block(toks: &[Token], marker_pos: usize) -> Option<(NurbsSurface, usize)> {
let cp_dims = toks::marker_at(toks, marker_pos)?.cp_dims();
let mut cur = Cur::at(toks, marker_pos + 1);
let degree_u = cur.take_long()?;
let degree_v = cur.take_long()?;
if !(1..=20).contains(°ree_u) || !(1..=20).contains(°ree_v) {
return None;
}
if matches!(cur.peek(), Some(Token::Ident(_))) {
cur.bump();
}
let mut enums = [0i64; 4];
for e in &mut enums {
*e = cur.take_enum()?;
}
let n_uniq_u = cur.take_long()?;
let n_uniq_v = cur.take_long()?;
if !(1..=1000).contains(&n_uniq_u) || !(1..=1000).contains(&n_uniq_v) {
return None;
}
let (u_knots, n_poles_u) = knots(&mut cur, n_uniq_u as usize, degree_u)?;
let (v_knots, n_poles_v) = knots(&mut cur, n_uniq_v as usize, degree_v)?;
if n_poles_u.checked_mul(n_poles_v).is_none_or(|n| n > 200_000) {
return None;
}
let (flat, flat_w) = control_points(&mut cur, n_poles_u * n_poles_v, cp_dims)?;
let mut grid = vec![Point3::new(0.0, 0.0, 0.0); n_poles_u * n_poles_v];
let mut weights = flat_w.as_ref().map(|_| vec![0.0f64; n_poles_u * n_poles_v]);
for v in 0..n_poles_v {
for u in 0..n_poles_u {
let file_idx = v * n_poles_u + u;
let ir_idx = u * n_poles_v + v;
grid[ir_idx] = flat[file_idx];
if let (Some(w), Some(fw)) = (weights.as_mut(), flat_w.as_ref()) {
w[ir_idx] = fw[file_idx];
}
}
}
Some((
NurbsSurface {
u_degree: degree_u as u32,
v_degree: degree_v as u32,
u_knots,
v_knots,
u_count: n_poles_u as u32,
v_count: n_poles_v as u32,
control_points: grid,
weights,
u_periodic: is_periodic(enums[0]),
v_periodic: is_periodic(enums[1]),
},
cur.pos(),
))
}
pub(crate) fn curve_block(toks: &[Token], marker_pos: usize) -> Option<(NurbsCurve, usize)> {
let cp_dims = toks::marker_at(toks, marker_pos)?.cp_dims();
let mut cur = Cur::at(toks, marker_pos + 1);
let degree = cur.take_long()?;
if !(1..=20).contains(°ree) {
return None;
}
let closure = cur.take_enum()?;
let n_uniq = cur.take_long()?;
if !(1..=1000).contains(&n_uniq) {
return None;
}
let (knot_vector, n_poles) = knots(&mut cur, n_uniq as usize, degree)?;
let (points, weights) = control_points(&mut cur, n_poles, cp_dims)?;
Some((
NurbsCurve {
degree: degree as u32,
knots: knot_vector,
control_points: points,
weights,
periodic: is_periodic(closure),
},
cur.pos(),
))
}
pub(crate) fn surface_cache(toks: &[Token]) -> Option<NurbsSurface> {
let mut caches = toks::marker_positions(toks)
.into_iter()
.filter_map(|pos| surface_block(toks, pos).map(|(surface, _)| surface));
let compound = toks.iter().any(|token| {
matches!(token, Token::Ident(name) | Token::SubIdent(name) if name == "comp_spl_sur")
});
if compound {
caches.next()
} else {
caches.next_back()
}
}
pub(crate) fn owned_surface_cache(scope: &[Token]) -> Option<NurbsSurface> {
toks::owned_marker_positions(scope)
.into_iter()
.find_map(|pos| surface_block(scope, pos).map(|(surface, _)| surface))
}
pub(crate) fn curve_cache(toks: &[Token]) -> Option<NurbsCurve> {
toks::marker_positions(toks)
.into_iter()
.find_map(|pos| curve_block(toks, pos).map(|(curve, _)| curve))
}
pub(crate) fn owned_curve_cache(scope: &[Token]) -> Option<NurbsCurve> {
toks::owned_marker_positions(scope)
.into_iter()
.find_map(|pos| curve_block(scope, pos).map(|(curve, _)| curve))
}
pub(crate) fn cache_resolving_refs<T>(
toks: &[Token],
table: &toks::SubtypeTable,
seen: &mut Vec<usize>,
decode_inline: fn(&[Token]) -> Option<T>,
) -> Option<T> {
if let Some(decoded) = decode_inline(toks) {
return Some(decoded);
}
for index in toks::subtype_refs(toks) {
if seen.contains(&index) {
continue;
}
seen.push(index);
let target = table.span(index)?;
if let Some(decoded) = cache_resolving_refs(target, table, seen, decode_inline) {
return Some(decoded);
}
}
None
}
pub fn surface_cache_resolving_refs(
toks: &[Token],
table: &toks::SubtypeTable,
) -> Option<NurbsSurface> {
cache_resolving_refs(toks, table, &mut Vec::new(), surface_cache)
}
pub(crate) fn owned_surface_cache_resolving_refs(
toks: &[Token],
table: &toks::SubtypeTable,
) -> Option<NurbsSurface> {
cache_resolving_refs(toks, table, &mut Vec::new(), owned_surface_cache)
}
pub fn curve_cache_resolving_refs(
toks: &[Token],
table: &toks::SubtypeTable,
) -> Option<NurbsCurve> {
cache_resolving_refs(toks, table, &mut Vec::new(), curve_cache)
}
pub(crate) fn owned_curve_cache_resolving_refs(
toks: &[Token],
table: &toks::SubtypeTable,
) -> Option<NurbsCurve> {
cache_resolving_refs(toks, table, &mut Vec::new(), owned_curve_cache)
}
pub(crate) struct DecodedSurfaceBlock {
pub(crate) surface: NurbsSurface,
pub(crate) end: usize,
control_value_offsets: Vec<usize>,
rational: bool,
u_knot_layout: KnotLayout,
v_knot_layout: KnotLayout,
periodic_value_offsets: [usize; 2],
degree_value_offsets: [usize; 2],
int_width: usize,
}
pub(crate) fn decode_surface_block(
b: &[u8],
marker_pos: usize,
int_width: usize,
) -> Option<DecodedSurfaceBlock> {
let (cp_dims, marker_len, rational) = marker_at(b, marker_pos)?;
let mut pos = marker_pos + marker_len;
let degree_u_offset = pos + 1;
let degree_u = take_tagged_int(b, &mut pos, 0x04, int_width)?;
let degree_v_offset = pos + 1;
let degree_v = take_tagged_int(b, &mut pos, 0x04, int_width)?;
if !(1..=20).contains(°ree_u) || !(1..=20).contains(°ree_v) {
return None;
}
if b.get(pos) == Some(&0x0d) {
let len = *b.get(pos + 1)? as usize;
pos += 2 + len;
}
let mut enums = [0i64; 4];
let mut enum_value_offsets = [0usize; 4];
for (ordinal, e) in enums.iter_mut().enumerate() {
enum_value_offsets[ordinal] = pos + 1;
*e = take_tagged_int(b, &mut pos, 0x15, int_width)?;
}
let n_uniq_u = take_tagged_int(b, &mut pos, 0x04, int_width)?;
let n_uniq_v = take_tagged_int(b, &mut pos, 0x04, int_width)?;
if !(1..=1000).contains(&n_uniq_u) || !(1..=1000).contains(&n_uniq_v) {
return None;
}
let (u_knots, n_poles_u, u_knot_layout) =
read_knots(b, &mut pos, n_uniq_u as usize, degree_u, int_width)?;
let (v_knots, n_poles_v, v_knot_layout) =
read_knots(b, &mut pos, n_uniq_v as usize, degree_v, int_width)?;
if n_poles_u.checked_mul(n_poles_v).is_none_or(|n| n > 200_000) {
return None;
}
let control_start = pos;
let (flat, flat_w) = read_control_points(b, &mut pos, n_poles_u * n_poles_v, cp_dims)?;
let control_value_offsets = (0..n_poles_u * n_poles_v * cp_dims)
.map(|ordinal| control_start + ordinal * 9 + 1)
.collect();
let mut control_points = vec![Point3::new(0.0, 0.0, 0.0); n_poles_u * n_poles_v];
let mut weights = flat_w.as_ref().map(|_| vec![0.0f64; n_poles_u * n_poles_v]);
for v in 0..n_poles_v {
for u in 0..n_poles_u {
let file_idx = v * n_poles_u + u;
let ir_idx = u * n_poles_v + v;
control_points[ir_idx] = flat[file_idx];
if let (Some(w), Some(fw)) = (weights.as_mut(), flat_w.as_ref()) {
w[ir_idx] = fw[file_idx];
}
}
}
Some(DecodedSurfaceBlock {
surface: NurbsSurface {
u_degree: degree_u as u32,
v_degree: degree_v as u32,
u_knots,
v_knots,
u_count: n_poles_u as u32,
v_count: n_poles_v as u32,
control_points,
weights,
u_periodic: is_periodic(enums[0]),
v_periodic: is_periodic(enums[1]),
},
end: pos,
control_value_offsets,
rational,
u_knot_layout,
v_knot_layout,
periodic_value_offsets: [enum_value_offsets[0], enum_value_offsets[1]],
degree_value_offsets: [degree_u_offset, degree_v_offset],
int_width,
})
}
pub struct SurfacePatchLayout {
pub int_width: usize,
pub control_value_offsets: Vec<usize>,
pub rational: bool,
pub u_count: usize,
pub v_count: usize,
pub u_knots: KnotPatchLayout,
pub v_knots: KnotPatchLayout,
pub end: usize,
pub periodic_value_offsets: [usize; 2],
pub degree_value_offsets: [usize; 2],
}
pub struct KnotPatchLayout {
pub value_offsets: Vec<usize>,
pub multiplicity_offsets: Vec<usize>,
#[expect(dead_code)]
pub(crate) expanded_run_lengths: Vec<usize>,
}
impl From<KnotLayout> for KnotPatchLayout {
fn from(value: KnotLayout) -> Self {
Self {
value_offsets: value.value_offsets,
multiplicity_offsets: value.multiplicity_offsets,
expanded_run_lengths: value.expanded_run_lengths,
}
}
}
pub fn final_surface_patch_layout(record: &[u8]) -> Option<SurfacePatchLayout> {
let decoded = INT_WIDTHS.into_iter().find_map(|int_width| {
marker_positions(record)
.into_iter()
.filter_map(|position| decode_surface_block(record, position, int_width))
.next_back()
})?;
Some(SurfacePatchLayout {
int_width: decoded.int_width,
control_value_offsets: decoded.control_value_offsets,
rational: decoded.rational,
u_count: decoded.surface.u_count as usize,
v_count: decoded.surface.v_count as usize,
u_knots: decoded.u_knot_layout.into(),
v_knots: decoded.v_knot_layout.into(),
end: decoded.end,
periodic_value_offsets: decoded.periodic_value_offsets,
degree_value_offsets: decoded.degree_value_offsets,
})
}
pub fn surface_patch_layout_at(record: &[u8], ordinal: usize) -> Option<SurfacePatchLayout> {
let decoded = INT_WIDTHS.into_iter().find_map(|int_width| {
marker_positions(record)
.into_iter()
.filter_map(|position| decode_surface_block(record, position, int_width))
.nth(ordinal)
})?;
Some(SurfacePatchLayout {
int_width: decoded.int_width,
control_value_offsets: decoded.control_value_offsets,
rational: decoded.rational,
u_count: decoded.surface.u_count as usize,
v_count: decoded.surface.v_count as usize,
u_knots: decoded.u_knot_layout.into(),
v_knots: decoded.v_knot_layout.into(),
end: decoded.end,
periodic_value_offsets: decoded.periodic_value_offsets,
degree_value_offsets: decoded.degree_value_offsets,
})
}
pub(crate) struct DecodedCurveBlock {
pub(crate) curve: NurbsCurve,
pub(crate) end: usize,
control_value_offsets: Vec<usize>,
rational: bool,
knot_layout: KnotLayout,
periodic_value_offset: usize,
degree_value_offset: usize,
int_width: usize,
}
pub(crate) fn decode_curve_block(
b: &[u8],
marker_pos: usize,
int_width: usize,
) -> Option<DecodedCurveBlock> {
let (cp_dims, marker_len, rational) = marker_at(b, marker_pos)?;
let mut pos = marker_pos + marker_len;
let degree_value_offset = pos + 1;
let degree = take_tagged_int(b, &mut pos, 0x04, int_width)?;
if !(1..=20).contains(°ree) {
return None;
}
let periodic_value_offset = pos + 1;
let closure = take_tagged_int(b, &mut pos, 0x15, int_width)?;
let n_uniq = take_tagged_int(b, &mut pos, 0x04, int_width)?;
if !(1..=1000).contains(&n_uniq) {
return None;
}
let (knots, n_poles, knot_layout) =
read_knots(b, &mut pos, n_uniq as usize, degree, int_width)?;
let control_start = pos;
let (control_points, weights) = read_control_points(b, &mut pos, n_poles, cp_dims)?;
let control_value_offsets = (0..n_poles * cp_dims)
.map(|ordinal| control_start + ordinal * 9 + 1)
.collect();
Some(DecodedCurveBlock {
curve: NurbsCurve {
degree: degree as u32,
knots,
control_points,
weights,
periodic: is_periodic(closure),
},
end: pos,
control_value_offsets,
rational,
knot_layout,
periodic_value_offset,
degree_value_offset,
int_width,
})
}
pub struct CurvePatchLayout {
pub int_width: usize,
pub control_value_offsets: Vec<usize>,
pub rational: bool,
pub control_count: usize,
pub knots: KnotPatchLayout,
pub end: usize,
pub periodic_value_offset: usize,
pub degree_value_offset: usize,
}
pub fn first_curve_patch_layout(record: &[u8]) -> Option<CurvePatchLayout> {
let decoded = INT_WIDTHS.into_iter().find_map(|int_width| {
marker_positions(record)
.into_iter()
.find_map(|position| decode_curve_block(record, position, int_width))
})?;
Some(CurvePatchLayout {
int_width: decoded.int_width,
control_count: decoded.curve.control_points.len(),
control_value_offsets: decoded.control_value_offsets,
rational: decoded.rational,
knots: decoded.knot_layout.into(),
end: decoded.end,
periodic_value_offset: decoded.periodic_value_offset,
degree_value_offset: decoded.degree_value_offset,
})
}
pub fn final_curve_patch_layout(record: &[u8]) -> Option<CurvePatchLayout> {
let decoded = INT_WIDTHS.into_iter().find_map(|int_width| {
marker_positions(record)
.into_iter()
.filter_map(|position| decode_curve_block(record, position, int_width))
.next_back()
})?;
Some(CurvePatchLayout {
int_width: decoded.int_width,
control_count: decoded.curve.control_points.len(),
control_value_offsets: decoded.control_value_offsets,
rational: decoded.rational,
knots: decoded.knot_layout.into(),
end: decoded.end,
periodic_value_offset: decoded.periodic_value_offset,
degree_value_offset: decoded.degree_value_offset,
})
}
pub fn decode_surface_cache(record_bytes: &[u8]) -> Option<NurbsSurface> {
INT_WIDTHS
.into_iter()
.find_map(|int_width| decode_surface_cache_at(record_bytes, int_width))
}
fn decode_surface_cache_at(record_bytes: &[u8], int_width: usize) -> Option<NurbsSurface> {
let caches = marker_positions(record_bytes)
.into_iter()
.filter_map(|pos| decode_surface_block(record_bytes, pos, int_width))
.map(|decoded| decoded.surface);
if record_bytes
.windows(b"comp_spl_sur".len())
.any(|window| window == b"comp_spl_sur")
{
caches.into_iter().next()
} else {
caches.into_iter().next_back()
}
}
pub(crate) fn decode_owned_surface_cache_at(
scope: &[u8],
int_width: usize,
) -> Option<NurbsSurface> {
owned_marker_positions(scope, int_width)
.into_iter()
.find_map(|pos| decode_surface_block(scope, pos, int_width).map(|decoded| decoded.surface))
}
pub(crate) fn decode_owned_surface_cache_resolving_refs_at(
scope: &[u8],
active_bytes: &[u8],
tables: &SubtypeTables,
int_width: usize,
) -> Option<NurbsSurface> {
decode_cache_resolving_refs(
scope,
active_bytes,
tables,
&mut Vec::new(),
decode_owned_surface_cache_at,
int_width,
)
}
pub fn decode_curve_cache(record_bytes: &[u8]) -> Option<NurbsCurve> {
INT_WIDTHS
.into_iter()
.find_map(|int_width| decode_curve_cache_at(record_bytes, int_width))
}
pub(crate) fn decode_curve_cache_at(record_bytes: &[u8], int_width: usize) -> Option<NurbsCurve> {
marker_positions(record_bytes).into_iter().find_map(|pos| {
decode_curve_block(record_bytes, pos, int_width).map(|decoded| decoded.curve)
})
}
pub fn decode_owned_curve_cache_at(scope: &[u8], int_width: usize) -> Option<NurbsCurve> {
owned_marker_positions(scope, int_width)
.into_iter()
.find_map(|pos| decode_curve_block(scope, pos, int_width).map(|decoded| decoded.curve))
}
pub(crate) fn decode_owned_curve_cache_resolving_refs_at(
scope: &[u8],
active_bytes: &[u8],
tables: &SubtypeTables,
int_width: usize,
) -> Option<NurbsCurve> {
decode_cache_resolving_refs(
scope,
active_bytes,
tables,
&mut Vec::new(),
decode_owned_curve_cache_at,
int_width,
)
}