use crate::nurbs::core::KnotPatchLayout;
use crate::nurbs::reader::{
construction_marker_positions, is_periodic, marker_at, marker_positions, read_knots,
take_tagged_int, INT_WIDTHS,
};
use crate::nurbs::toks::{self, Cur};
use crate::sab::Token;
use cadmpeg_core::decode::View;
use cadmpeg_ir::math::Point2;
#[derive(Clone)]
pub struct NurbsPcurve {
pub degree: u32,
pub knots: Vec<f64>,
pub control_points: Vec<Point2>,
pub weights: Option<Vec<f64>>,
pub periodic: bool,
}
pub struct PcurvePatchLayout {
pub int_width: usize,
pub degree_value_offset: usize,
pub control_value_offsets: Vec<usize>,
pub weight_value_offsets: Vec<usize>,
pub control_count: usize,
pub knots: KnotPatchLayout,
pub periodic_value_offset: usize,
pub control_end: usize,
}
pub fn final_pcurve_patch_layout(record: &[u8], int_width: usize) -> Option<PcurvePatchLayout> {
final_pcurve_patch_layout_at(record, int_width)
}
fn final_pcurve_patch_layout_at(record: &[u8], int_width: usize) -> Option<PcurvePatchLayout> {
construction_marker_positions(record, int_width)
.into_iter()
.filter_map(|marker_pos| {
let (_cp_dims, marker_len, rational) = marker_at(record, marker_pos)?;
let mut pos = marker_pos + marker_len;
let degree_value_offset = pos + 1;
let degree = take_tagged_int(record, &mut pos, 0x04, int_width)?;
if !(1..=20).contains(°ree) {
return None;
}
let periodic_value_offset = pos + 1;
let _closure = take_tagged_int(record, &mut pos, 0x15, int_width)?;
let unique = take_tagged_int(record, &mut pos, 0x04, int_width)?;
if !(1..=1000).contains(&unique) {
return None;
}
let (_knots, control_count, knot_layout) =
read_knots(record, &mut pos, unique as usize, degree, int_width)?;
let mut offsets = Vec::with_capacity(control_count * 2);
let mut weight_offsets = Vec::with_capacity(control_count * usize::from(rational));
for _ in 0..control_count * 2 {
if record.get(pos) != Some(&0x06) {
return None;
}
offsets.push(pos + 1);
pos += 9;
if rational && offsets.len() % 2 == 0 {
if record.get(pos) != Some(&0x06) {
return None;
}
weight_offsets.push(pos + 1);
pos += 9;
}
}
Some(PcurvePatchLayout {
int_width,
degree_value_offset,
control_value_offsets: offsets,
weight_value_offsets: weight_offsets,
control_count,
knots: knot_layout.into(),
periodic_value_offset,
control_end: pos,
})
})
.next_back()
}
fn decode_pcurve_block(b: &[u8], marker_pos: usize, int_width: usize) -> Option<NurbsPcurve> {
decode_pcurve_block_with_end(b, marker_pos, int_width).map(|(pcurve, _)| pcurve)
}
pub(crate) fn decode_pcurve_block_with_end(
b: &[u8],
marker_pos: usize,
int_width: usize,
) -> Option<(NurbsPcurve, usize)> {
let (_cp_dims, marker_len, rational) = marker_at(b, marker_pos)?;
let mut pos = marker_pos + marker_len;
let degree = take_tagged_int(b, &mut pos, 0x04, int_width)?;
if !(1..=20).contains(°ree) {
return None;
}
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 mut control_points = Vec::with_capacity(n_poles);
let mut weights = rational.then(|| Vec::with_capacity(n_poles));
for _ in 0..n_poles {
if *b.get(pos)? != 0x06 {
return None;
}
let u = View::f64_le_at(b, pos + 1)?;
pos += 9;
if *b.get(pos)? != 0x06 {
return None;
}
let v = View::f64_le_at(b, pos + 1)?;
pos += 9;
control_points.push(Point2::new(u, v));
if let Some(weights) = weights.as_mut() {
if *b.get(pos)? != 0x06 {
return None;
}
weights.push(View::f64_le_at(b, pos + 1)?);
pos += 9;
}
}
Some((
NurbsPcurve {
degree: degree as u32,
knots,
control_points,
weights,
periodic: is_periodic(closure),
},
pos,
))
}
pub fn decode_pcurve_cache(record_bytes: &[u8]) -> Option<NurbsPcurve> {
let mut decoded = None;
for int_width in INT_WIDTHS {
for position in marker_positions(record_bytes) {
if let Some(candidate) = decode_pcurve_block(record_bytes, position, int_width) {
if decoded.is_some() {
return None;
}
decoded = Some(candidate);
}
}
}
decoded
}
pub(crate) fn pcurve_block_with_end(
toks: &[Token],
marker_pos: usize,
) -> Option<(NurbsPcurve, usize)> {
let rational = toks::marker_at(toks, marker_pos)?.rational();
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 (knots, n_poles) = toks::take_knot_table(&mut cur, n_uniq as usize, degree)?;
let mut control_points = Vec::new();
let mut weights = rational.then(Vec::new);
for _ in 0..n_poles {
let u = cur.take_f64()?;
let v = cur.take_f64()?;
control_points.push(Point2::new(u, v));
if let Some(weights) = weights.as_mut() {
weights.push(cur.take_f64()?);
}
}
Some((
NurbsPcurve {
degree: degree as u32,
knots,
control_points,
weights,
periodic: is_periodic(closure),
},
cur.pos(),
))
}
fn pcurve_block(toks: &[Token], marker_pos: usize) -> Option<NurbsPcurve> {
pcurve_block_with_end(toks, marker_pos).map(|(pcurve, _)| pcurve)
}
pub fn explicit_pcurve_cache(toks: &[Token]) -> Option<NurbsPcurve> {
let position = toks::owned_marker_positions(toks).into_iter().next()?;
pcurve_block(toks, position)
}
pub fn explicit_pcurve_cache_from_subtype_ref(
index: i64,
table: &toks::SubtypeTable,
) -> Option<NurbsPcurve> {
let index = usize::try_from(index).ok()?;
explicit_pcurve_cache(table.span(index)?)
}
pub fn pcurve_fit_tolerance(toks: &[Token]) -> Option<f64> {
let scope = toks::owned_cache_scope(toks).unwrap_or(toks);
let (_, end) = toks::owned_marker_positions(scope)
.into_iter()
.filter_map(|pos| pcurve_block_with_end(scope, pos))
.next_back()?;
match scope.get(end) {
Some(Token::Double(value)) => Some(*value),
_ => None,
}
}
#[cfg(test)]
mod tests;