use cadmpeg_core::le::{f64_at as read_f64, int_at as read_int, u16_at};
use cadmpeg_ir::math::{Point3, Vector3};
pub const LEN_TO_MM: f64 = 10.0;
pub(crate) fn unit_vector(vector: Vector3) -> Option<Vector3> {
let norm = vector.norm();
(norm.is_finite() && norm > 0.0)
.then(|| Vector3::new(vector.x / norm, vector.y / norm, vector.z / norm))
}
pub(crate) const NUBS_MARKER: &[u8] = b"\x0d\x04nubs";
const NURBS_MARKER: &[u8] = b"\x0d\x05nurbs";
pub(crate) const INT_WIDTHS: [usize; 2] = [8, 4];
pub(crate) fn take_tagged_int(b: &[u8], pos: &mut usize, tag: u8, int_width: usize) -> Option<i64> {
if *b.get(*pos)? != tag {
return None;
}
let v = read_int(b, *pos + 1, int_width)?;
*pos += 1 + int_width;
Some(v)
}
pub(crate) fn marker_at(b: &[u8], pos: usize) -> Option<(usize, usize, bool)> {
if b[pos..].starts_with(NUBS_MARKER) {
Some((3, NUBS_MARKER.len(), false))
} else if b[pos..].starts_with(NURBS_MARKER) {
Some((4, NURBS_MARKER.len(), true))
} else {
None
}
}
pub(crate) fn marker_positions(b: &[u8]) -> Vec<usize> {
let mut out = Vec::new();
if b.len() < NUBS_MARKER.len() {
return out;
}
for pos in 0..=b.len() - NUBS_MARKER.len() {
if marker_at(b, pos).is_some() {
out.push(pos);
}
}
out
}
pub(crate) fn owned_marker_positions(b: &[u8], int_width: usize) -> Vec<usize> {
let mut out = Vec::new();
let mut depth = 0usize;
let mut pos = usize::from(b.first() == Some(&0x0f));
while pos < b.len() {
match b[pos] {
0x0f => depth += 1,
0x10 => depth = depth.saturating_sub(1),
_ => {
if depth == 0 && marker_at(b, pos).is_some() {
out.push(pos);
}
}
}
match crate::nurbs::subtypes::next_token(b, pos, int_width) {
Some(next) => pos = next,
None => break,
}
}
out
}
pub(crate) struct KnotLayout {
pub(crate) value_offsets: Vec<usize>,
pub(crate) multiplicity_offsets: Vec<usize>,
pub(crate) expanded_run_lengths: Vec<usize>,
}
pub(crate) fn read_knots(
b: &[u8],
pos: &mut usize,
n: usize,
degree: i64,
int_width: usize,
) -> Option<(Vec<f64>, usize, KnotLayout)> {
let mut knots = Vec::new();
let mut mults = Vec::new();
let mut value_offsets = Vec::new();
let mut multiplicity_offsets = Vec::new();
for _ in 0..n {
if *b.get(*pos)? != 0x06 {
return None;
}
value_offsets.push(*pos + 1);
knots.push(read_f64(b, *pos + 1)?);
*pos += 9;
multiplicity_offsets.push(*pos + 1);
mults.push(take_tagged_int(b, pos, 0x04, int_width)?);
}
let sum: i64 = mults.iter().sum();
let n_poles = sum - (degree - 1);
if !(2..=100_000).contains(&n_poles) {
return None;
}
let mut expanded = Vec::new();
let mut expanded_run_lengths = Vec::new();
for (i, (kv, m)) in knots.iter().zip(&mults).enumerate() {
let extra = i64::from(i == 0 || i == n - 1);
let run_length = usize::try_from((*m + extra).max(0)).ok()?;
expanded_run_lengths.push(run_length);
for _ in 0..run_length {
expanded.push(*kv);
}
}
Some((
expanded,
n_poles as usize,
KnotLayout {
value_offsets,
multiplicity_offsets,
expanded_run_lengths,
},
))
}
pub(crate) fn read_control_points(
b: &[u8],
pos: &mut usize,
count: usize,
cp_dims: usize,
) -> Option<(Vec<Point3>, Option<Vec<f64>>)> {
let mut points = Vec::with_capacity(count);
let mut weights = if cp_dims == 4 {
Some(Vec::with_capacity(count))
} else {
None
};
for _ in 0..count {
let mut comps = [0.0f64; 4];
for comp in comps.iter_mut().take(cp_dims) {
if *b.get(*pos)? != 0x06 {
return None;
}
*comp = read_f64(b, *pos + 1)?;
*pos += 9;
}
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 is_periodic(enum_val: i64) -> bool {
enum_val == 2
}
pub(crate) enum Nullable<T> {
Null,
Value(T),
}
impl<T> Nullable<T> {
pub(crate) fn value(self) -> Option<T> {
match self {
Self::Null => None,
Self::Value(value) => Some(value),
}
}
}
pub(crate) fn take_double_payload(bytes: &[u8], position: &mut usize) -> Option<usize> {
(*bytes.get(*position)? == 0x06).then_some(())?;
let payload = *position + 1;
bytes.get(payload..payload + 8)?;
*position = payload + 8;
Some(payload)
}
pub(crate) fn take_float_array_payloads(
bytes: &[u8],
position: &mut usize,
int_width: usize,
) -> Option<Vec<usize>> {
(*bytes.get(*position)? == 0x04).then_some(())?;
let raw = bytes.get(*position + 1..*position + 1 + int_width)?;
let count = match int_width {
8 => usize::try_from(i64::from_le_bytes(raw.try_into().ok()?)).ok()?,
4 => usize::try_from(i32::from_le_bytes(raw.try_into().ok()?)).ok()?,
_ => return None,
};
*position += 1 + int_width;
(0..count)
.map(|_| take_double_payload(bytes, position))
.collect()
}
pub(crate) fn take_f64(bytes: &[u8], position: &mut usize) -> Option<f64> {
if bytes.get(*position) != Some(&0x06) {
return None;
}
let value = read_f64(bytes, *position + 1)?;
*position += 9;
Some(value)
}
pub(crate) fn take_bool(bytes: &[u8], position: &mut usize) -> Option<bool> {
let value = match bytes.get(*position)? {
0x0a => true,
0x0b => false,
_ => return None,
};
*position += 1;
Some(value)
}
pub(crate) fn normalized(value: [f64; 3]) -> Option<Vector3> {
let length = value
.iter()
.map(|component| component * component)
.sum::<f64>()
.sqrt();
(length.is_finite() && length > 0.0)
.then(|| Vector3::new(value[0] / length, value[1] / length, value[2] / length))
}
pub(crate) fn take_native_ident(bytes: &[u8], position: &mut usize) -> Option<String> {
if !matches!(bytes.get(*position), Some(0x0d | 0x0e)) {
return None;
}
let length = usize::from(*bytes.get(*position + 1)?);
let start = *position + 2;
let end = start.checked_add(length)?;
let value = String::from_utf8(bytes.get(start..end)?.to_vec()).ok()?;
*position = end;
Some(value)
}
pub(crate) fn take_native_string(
bytes: &[u8],
position: &mut usize,
int_width: usize,
) -> Option<String> {
let (length, header) = match *bytes.get(*position)? {
0x07 => (usize::from(*bytes.get(*position + 1)?), 2),
0x08 => (usize::from(u16_at(bytes, *position + 1)?), 3),
0x09 => (
usize::try_from(read_int(bytes, *position + 1, int_width)?).ok()?,
1 + int_width,
),
_ => return None,
};
let start = *position + header;
let end = start.checked_add(length)?;
let value = String::from_utf8(bytes.get(start..end)?.to_vec()).ok()?;
*position = end;
Some(value)
}
pub(crate) fn take_range_value(bytes: &[u8], position: &mut usize) -> Option<f64> {
if matches!(bytes.get(*position), Some(0x0a | 0x0b)) {
*position += 1;
}
if bytes.get(*position) != Some(&0x06) {
return None;
}
let value = read_f64(bytes, *position + 1)?;
*position += 9;
Some(value)
}
#[allow(clippy::option_option)] pub(crate) fn take_optional_range_value(bytes: &[u8], position: &mut usize) -> Option<Option<f64>> {
match bytes.get(*position)? {
0x0a => {
*position += 1;
take_f64(bytes, position).map(Some)
}
0x0b => {
*position += 1;
Some(None)
}
0x06 => take_f64(bytes, position).map(Some),
_ => None,
}
}
pub(crate) fn take_native_vec3(bytes: &[u8], position: &mut usize, tag: u8) -> Option<[f64; 3]> {
if bytes.get(*position) != Some(&tag) {
return None;
}
let values = [
read_f64(bytes, *position + 1)?,
read_f64(bytes, *position + 9)?,
read_f64(bytes, *position + 17)?,
];
*position += 25;
Some(values)
}
#[cfg(test)]
mod string_width_tests {
use super::take_native_string;
fn long_string_bytes(payload: &str, int_width: usize) -> Vec<u8> {
let mut bytes = vec![0x09];
let mut length = (payload.len() as u64).to_le_bytes().to_vec();
length.truncate(int_width);
bytes.extend_from_slice(&length);
bytes.extend_from_slice(payload.as_bytes());
bytes
}
#[test]
fn long_string_length_prefix_is_the_stream_int_width() {
for int_width in [4usize, 8] {
let bytes = long_string_bytes("#TS0200\ndegree 3", int_width);
let mut position = 0;
let value = take_native_string(&bytes, &mut position, int_width)
.unwrap_or_else(|| panic!("string at width {int_width}"));
assert_eq!(value, "#TS0200\ndegree 3");
assert_eq!(position, bytes.len());
}
}
#[test]
fn long_string_read_at_the_wrong_width_never_yields_the_payload() {
let bytes = long_string_bytes("#TS0200\ndegree 3", 8);
let mut position = 0;
let value = take_native_string(&bytes, &mut position, 4);
assert_ne!(value.as_deref(), Some("#TS0200\ndegree 3"));
}
}