use crate::kernel_header::RefWidth;
use crate::nurbs::reader::{
construction_marker_positions, is_periodic, marker_at, marker_positions, read_knots,
take_tagged_int, KnotLayout, INT_WIDTHS,
};
use crate::nurbs::toks::{self, Cur};
use crate::sab::Token;
use cadmpeg_core::decode::View;
use cadmpeg_ir::geometry::pcurve::{PcurveNurbs, PcurveNurbsPoles, WeightedPole2};
use cadmpeg_ir::math::Point2;
use cadmpeg_ir::scalar::NonZeroReal;
use cadmpeg_ir::units::FinitePoint2;
pub struct PcurvePatchLayout {
pub degree_value_offset: usize,
control_start: usize,
rational: bool,
pub control_count: usize,
pub knots: KnotLayout,
pub periodic_value_offset: usize,
}
impl PcurvePatchLayout {
fn control_stride(&self) -> usize {
if self.rational {
27
} else {
18
}
}
pub fn rational(&self) -> bool {
self.rational
}
pub fn control_value_offsets(&self) -> impl ExactSizeIterator<Item = [usize; 2]> + '_ {
(0..self.control_count).map(|ordinal| {
let u = self.control_start + ordinal * self.control_stride() + 1;
[u, u + 9]
})
}
pub fn weight_value_offsets(&self) -> impl ExactSizeIterator<Item = usize> + '_ {
(0..if self.rational { self.control_count } else { 0 })
.map(|ordinal| self.control_start + ordinal * 27 + 19)
}
pub fn control_end(&self) -> usize {
self.control_start + self.control_count * self.control_stride()
}
}
pub fn final_pcurve_patch_layout(record: &[u8], int_width: RefWidth) -> Option<PcurvePatchLayout> {
construction_marker_positions(record, int_width)?
.into_iter()
.filter_map(|marker_pos| {
let marker = marker_at(record, marker_pos)?;
let rational = marker.rational();
let mut pos = marker_pos + marker.byte_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,
usize::try_from(unique).ok()?,
degree,
int_width,
)?;
let control_start = pos;
let components = if rational { 3 } else { 2 };
for _ in 0..control_count * components {
if record.get(pos) != Some(&0x06) {
return None;
}
pos += 9;
}
Some(PcurvePatchLayout {
degree_value_offset,
control_start,
rational,
control_count,
knots: knot_layout,
periodic_value_offset,
})
})
.next_back()
}
fn decode_pcurve_block(b: &[u8], marker_pos: usize, int_width: RefWidth) -> Option<PcurveNurbs> {
decode_pcurve_block_with_end(b, marker_pos, int_width).map(|(pcurve, _)| pcurve)
}
pub(super) fn decode_pcurve_block_with_end(
b: &[u8],
marker_pos: usize,
int_width: RefWidth,
) -> Option<(PcurveNurbs, usize)> {
let marker = marker_at(b, marker_pos)?;
let rational = marker.rational();
let mut pos = marker_pos + marker.byte_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,
usize::try_from(n_uniq).ok()?,
degree,
int_width,
)?;
let mut points = Vec::new();
let mut weighted = Vec::new();
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;
let point = Point2::new(u, v);
if rational {
if *b.get(pos)? != 0x06 {
return None;
}
let weight = View::f64_le_at(b, pos + 1)?;
pos += 9;
weighted.push(WeightedPole2 {
point,
weight: NonZeroReal::new(weight)?,
});
} else {
points.push(point);
}
}
let poles = if rational {
PcurveNurbsPoles::Rational { points: weighted }
} else {
PcurveNurbsPoles::Polynomial { points }
};
Some((
PcurveNurbs::new(
u32::try_from(degree).ok()?,
knots,
poles,
is_periodic(closure),
)
.ok()?,
pos,
))
}
pub fn decode_pcurve_cache(record_bytes: &[u8]) -> Option<PcurveNurbs> {
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(super) fn pcurve_block_with_end(
toks: &[Token],
marker_pos: usize,
) -> Option<(PcurveNurbs, 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, usize::try_from(n_uniq).ok()?, degree)?;
let mut points = Vec::new();
let mut weighted = Vec::new();
if rational {
weighted = Vec::new();
} else {
points = Vec::new();
}
for _ in 0..n_poles {
let u = cur.take_f64()?;
let v = cur.take_f64()?;
let point = FinitePoint2::new(Point2::new(u, v))?;
if rational {
weighted.push(WeightedPole2 {
point,
weight: NonZeroReal::new(cur.take_f64()?)?,
});
} else {
points.push(point);
}
}
let poles = if rational {
PcurveNurbsPoles::Rational { points: weighted }
} else {
PcurveNurbsPoles::Polynomial { points }
};
Some((
PcurveNurbs::new(
u32::try_from(degree).ok()?,
knots,
poles,
is_periodic(closure),
)
.ok()?,
cur.pos(),
))
}
fn pcurve_block(toks: &[Token], marker_pos: usize) -> Option<PcurveNurbs> {
pcurve_block_with_end(toks, marker_pos).map(|(pcurve, _)| pcurve)
}
pub fn explicit_pcurve_cache(scope: toks::SubtypeScope<'_>) -> Option<PcurveNurbs> {
let position = scope.owned_marker_positions().into_iter().next()?;
pcurve_block(scope.tokens(), position)
}
pub fn explicit_pcurve_cache_from_subtype_ref(
index: i64,
table: &toks::SubtypeTable,
) -> Option<PcurveNurbs> {
let index = usize::try_from(index).ok()?;
explicit_pcurve_cache(table.span(index)?)
}
pub fn pcurve_fit_tolerance(scope: toks::SubtypeScope<'_>) -> Option<f64> {
let tokens = scope.tokens();
let (_, end) = scope
.owned_marker_positions()
.into_iter()
.rev()
.find_map(|pos| pcurve_block_with_end(tokens, pos))?;
match tokens.get(end) {
Some(Token::Double(value)) => Some(*value),
_ => None,
}
}
#[cfg(test)]
mod tests;