use cadmpeg_ir::geometry::{
BlendCrossSection, BlendRadiusLaw, NurbsCurve, NurbsSurface, PcurveGeometry, SurfaceGeometry,
};
use cadmpeg_ir::le::{f64_at as read_f64, int_at as read_int, u16_at, u32_at};
use cadmpeg_ir::math::{Point2, Point3, Vector3};
use crate::sab::Record;
const LEN_TO_MM: f64 = 10.0;
const NUBS_MARKER: &[u8] = b"\x0d\x04nubs";
const NURBS_MARKER: &[u8] = b"\x0d\x05nurbs";
const INT_WIDTHS: [usize; 2] = [8, 4];
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)
}
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
}
}
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
}
struct KnotLayout {
value_offsets: Vec<usize>,
multiplicity_offsets: Vec<usize>,
expanded_run_lengths: Vec<usize>,
}
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,
},
))
}
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))
}
fn is_periodic(enum_val: i64) -> bool {
enum_val == 2
}
struct DecodedSurfaceBlock {
surface: NurbsSurface,
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],
}
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],
})
}
pub(crate) struct SurfacePatchLayout {
pub(crate) control_value_offsets: Vec<usize>,
pub(crate) rational: bool,
pub(crate) u_count: usize,
pub(crate) v_count: usize,
pub(crate) u_knots: KnotPatchLayout,
pub(crate) v_knots: KnotPatchLayout,
pub(crate) end: usize,
pub(crate) periodic_value_offsets: [usize; 2],
pub(crate) degree_value_offsets: [usize; 2],
}
pub(crate) struct KnotPatchLayout {
pub(crate) value_offsets: Vec<usize>,
pub(crate) 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(crate) 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 {
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) 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 {
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,
})
}
struct DecodedCurveBlock {
curve: NurbsCurve,
end: usize,
control_value_offsets: Vec<usize>,
rational: bool,
knot_layout: KnotLayout,
periodic_value_offset: usize,
degree_value_offset: usize,
}
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,
})
}
pub(crate) struct CurvePatchLayout {
pub(crate) control_value_offsets: Vec<usize>,
pub(crate) rational: bool,
pub(crate) control_count: usize,
pub(crate) knots: KnotPatchLayout,
pub(crate) end: usize,
pub(crate) periodic_value_offset: usize,
pub(crate) degree_value_offset: usize,
}
pub(crate) 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 {
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(crate) 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 {
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 struct NurbsPcurve {
pub degree: u32,
pub knots: Vec<f64>,
pub control_points: Vec<Point2>,
pub weights: Option<Vec<f64>>,
pub periodic: bool,
}
pub(crate) struct PcurvePatchLayout {
pub(crate) degree_value_offset: usize,
pub(crate) control_value_offsets: Vec<usize>,
pub(crate) weight_value_offsets: Vec<usize>,
pub(crate) control_count: usize,
pub(crate) knots: KnotPatchLayout,
pub(crate) periodic_value_offset: usize,
pub(crate) control_end: usize,
}
pub(crate) fn final_pcurve_patch_layout(record: &[u8]) -> Option<PcurvePatchLayout> {
INT_WIDTHS
.into_iter()
.find_map(|int_width| final_pcurve_patch_layout_at(record, int_width))
}
fn final_pcurve_patch_layout_at(record: &[u8], int_width: usize) -> Option<PcurvePatchLayout> {
marker_positions(record)
.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 {
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()
}
pub(crate) fn decode_pcurve_fit_tolerance(record: &[u8]) -> Option<f64> {
let layout = final_pcurve_patch_layout(record)?;
(record.get(layout.control_end) == Some(&0x06))
.then(|| read_f64(record, layout.control_end + 1))
.flatten()
}
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)
}
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 = read_f64(b, pos + 1)?;
pos += 9;
if *b.get(pos)? != 0x06 {
return None;
}
let v = read_f64(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(read_f64(b, pos + 1)?);
pos += 9;
}
}
Some((
NurbsPcurve {
degree: degree as u32,
knots,
control_points,
weights,
periodic: is_periodic(closure),
},
pos,
))
}
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 struct DecodedProceduralSurface {
pub definition: DecodedProceduralSurfaceDefinition,
pub cache_fit_tolerance: Option<f64>,
}
pub enum DecodedProceduralSurfaceDefinition {
Exact {
parameter_ranges: [[f64; 2]; 2],
extension: i64,
},
Compound {
parameters: Vec<f64>,
components: Vec<SurfaceGeometry>,
},
Taper {
support: SurfaceGeometry,
reference: NurbsCurve,
pcurve: Option<NurbsPcurve>,
parameter: f64,
taper: cadmpeg_ir::geometry::TaperSurfaceKind,
},
Loft(EmbeddedLoft),
CompoundLoft(Box<EmbeddedCompoundLoft>),
G2Blend(Box<EmbeddedG2Blend>),
Ruled {
first: NurbsCurve,
second: NurbsCurve,
},
Sum {
first: NurbsCurve,
second: NurbsCurve,
basepoint: Vector3,
},
Revolution {
directrix: NurbsCurve,
axis_origin: Point3,
axis_direction: Vector3,
angular_interval: [f64; 2],
parameter_interval: [f64; 2],
},
Offset {
support: SurfaceGeometry,
distance: f64,
u_sense: i64,
v_sense: i64,
extension_flags: Vec<bool>,
},
Extrusion {
directrix: NurbsCurve,
direction: Vector3,
},
Blend {
supports: Box<[Option<NurbsSurface>; 2]>,
spine: Option<NurbsCurve>,
radius: BlendRadiusLaw,
cross_section: BlendCrossSection,
native: Option<Box<EmbeddedRollingBall>>,
},
VariableBlend(Box<EmbeddedVariableBlend>),
VertexBlend(Box<EmbeddedVertexBlend>),
}
pub(crate) struct EmbeddedRollingBallSide {
pub(crate) label: String,
pub(crate) surface: Option<SurfaceGeometry>,
pub(crate) curve: NurbsCurve,
pub(crate) pcurve: Option<NurbsPcurve>,
pub(crate) location: Point3,
pub(crate) secondary_pcurve: Option<NurbsPcurve>,
pub(crate) exact_support: Option<NurbsSurface>,
}
pub(crate) struct EmbeddedRollingBallThirdSide {
pub(crate) label: String,
pub(crate) surface: SurfaceGeometry,
pub(crate) curve: NurbsCurve,
pub(crate) pcurve: Option<NurbsPcurve>,
pub(crate) direction: Vector3,
pub(crate) secondary_pcurve: Option<NurbsPcurve>,
pub(crate) extension: i64,
pub(crate) tertiary_pcurve: Option<NurbsPcurve>,
pub(crate) flag: bool,
}
pub(crate) struct EmbeddedVariableBlendSide {
pub(crate) label: String,
pub(crate) surface: SurfaceGeometry,
pub(crate) curve: NurbsCurve,
pub(crate) pcurve: Option<NurbsPcurve>,
pub(crate) location: Point3,
pub(crate) secondary_pcurve: Option<NurbsPcurve>,
pub(crate) scalar: f64,
pub(crate) tertiary_pcurve: Option<NurbsPcurve>,
}
pub struct EmbeddedVariableBlend {
pub(crate) sides: Box<[EmbeddedVariableBlendSide; 2]>,
pub(crate) primary_curve: NurbsCurve,
pub(crate) offsets: [f64; 2],
pub(crate) radius_kind: i64,
pub(crate) first_value: cadmpeg_ir::geometry::VariableBlendValue,
pub(crate) second_value: Option<cadmpeg_ir::geometry::VariableBlendValue>,
pub(crate) chamfer: Option<Box<cadmpeg_ir::geometry::VariableBlendChamfer>>,
pub(crate) single_radius_tail: Option<cadmpeg_ir::geometry::VariableBlendSingleRadiusTail>,
pub(crate) u_range: [f64; 2],
pub(crate) v_range: [f64; 2],
pub(crate) shape_prefix: i64,
pub(crate) shape_parameter: f64,
pub(crate) shape_length: f64,
pub(crate) shape_tail: i64,
pub(crate) shape_extensions: [i64; 3],
pub(crate) secondary_curve: NurbsCurve,
pub(crate) convexity: i64,
pub(crate) render_blend: i64,
pub(crate) post_range: [f64; 2],
pub(crate) post_curve: NurbsCurve,
pub(crate) post_pcurve: Option<NurbsPcurve>,
}
pub(crate) enum EmbeddedVertexBlendBoundaryGeometry {
Circle {
curve: NurbsCurve,
form: i64,
twists: Vec<Point3>,
parameters: [f64; 2],
sense: i64,
},
Degenerate {
location: Point3,
normals: [Vector3; 2],
},
Pcurve {
surface: SurfaceGeometry,
pcurve: Option<NurbsPcurve>,
sense: i64,
fit_tolerance: f64,
},
Plane {
normal: Vector3,
parameters: [f64; 2],
curve: NurbsCurve,
},
}
pub(crate) struct EmbeddedVertexBlendBoundary {
pub(crate) boundary_type: i64,
pub(crate) magic: Point3,
pub(crate) u_smoothing: i64,
pub(crate) v_smoothing: i64,
pub(crate) fullness: f64,
pub(crate) geometry: EmbeddedVertexBlendBoundaryGeometry,
}
pub struct EmbeddedVertexBlend {
pub(crate) boundaries: Vec<EmbeddedVertexBlendBoundary>,
pub(crate) grid_size: i64,
pub(crate) fit_tolerance: f64,
}
pub(crate) enum EmbeddedRollingBallRadiusSelector {
None,
Value(f64),
}
pub struct EmbeddedRollingBall {
pub(crate) sides: Box<[EmbeddedRollingBallSide; 2]>,
pub(crate) slice: NurbsCurve,
pub(crate) offsets: [f64; 2],
pub(crate) radius_selector: EmbeddedRollingBallRadiusSelector,
pub(crate) u_range: [f64; 2],
pub(crate) v_range: [f64; 2],
pub(crate) parameters: [f64; 3],
pub(crate) tail: i64,
pub(crate) discontinuities: [Vec<f64>; 3],
pub(crate) third: Option<Box<EmbeddedRollingBallThirdSide>>,
}
pub(crate) struct EmbeddedG2Side {
pub(crate) label: String,
pub(crate) surface: SurfaceGeometry,
pub(crate) curve: NurbsCurve,
pub(crate) pcurves: [Option<NurbsPcurve>; 2],
pub(crate) direction: Vector3,
}
pub(crate) enum EmbeddedG2FirstShape {
Full {
surface: Option<NurbsSurface>,
tolerance: Option<f64>,
},
None {
coefficients: [f64; 9],
tolerance: f64,
extension: Option<cadmpeg_ir::geometry::LoftBridgeToken>,
pcurve: Option<NurbsPcurve>,
},
}
pub struct EmbeddedG2Blend {
pub(crate) first: EmbeddedG2Side,
pub(crate) singularity: i64,
pub(crate) first_shape: EmbeddedG2FirstShape,
pub(crate) second: EmbeddedG2Side,
pub(crate) second_exact_surface: NurbsSurface,
pub(crate) center_curve: NurbsCurve,
pub(crate) center_parameters: [f64; 2],
pub(crate) center_flag: i64,
pub(crate) parameter_ranges: [[f64; 2]; 2],
pub(crate) trailing_parameters: [f64; 4],
pub(crate) discontinuities: [Vec<f64>; 3],
}
#[allow(clippy::option_option)] fn decode_nullable_embedded_pcurve(
bytes: &[u8],
position: &mut usize,
int_width: usize,
) -> Option<Option<NurbsPcurve>> {
let saved = *position;
if take_native_ident(bytes, position).as_deref() == Some("nullbs") {
return Some(None);
}
*position = saved;
let (pcurve, end) = decode_pcurve_block_with_end(bytes, *position, int_width)?;
*position = end;
Some(Some(pcurve))
}
fn decode_g2_side(bytes: &[u8], position: &mut usize, int_width: usize) -> Option<EmbeddedG2Side> {
let label = take_native_string(bytes, position)?;
let surface = decode_embedded_surface(bytes, position, int_width)?;
let curve = decode_curve_block(bytes, *position, int_width)?;
*position = curve.end;
let first = decode_nullable_embedded_pcurve(bytes, position, int_width)?;
let direction = take_native_vec3(bytes, position, 0x14)?;
let second = decode_nullable_embedded_pcurve(bytes, position, int_width)?;
Some(EmbeddedG2Side {
label,
surface,
curve: curve.curve,
pcurves: [first, second],
direction: Vector3::new(direction[0], direction[1], direction[2]),
})
}
fn take_bridge_token(
bytes: &[u8],
position: &mut usize,
int_width: usize,
) -> Option<cadmpeg_ir::geometry::LoftBridgeToken> {
use cadmpeg_ir::geometry::LoftBridgeToken;
match *bytes.get(*position)? {
0x0a | 0x0b => Some(LoftBridgeToken::Boolean(take_bool(bytes, position)?)),
0x04 => Some(LoftBridgeToken::Integer(take_tagged_int(
bytes, position, 0x04, int_width,
)?)),
0x06 => Some(LoftBridgeToken::Double(take_f64(bytes, position)?)),
0x15 => Some(LoftBridgeToken::Enum(take_tagged_int(
bytes, position, 0x15, int_width,
)?)),
0x07..=0x09 => Some(LoftBridgeToken::Text(take_native_string(bytes, position)?)),
_ => None,
}
}
fn decode_g2_blend_spl_sur(
record_bytes: &[u8],
int_width: usize,
) -> Option<DecodedProceduralSurface> {
let names: [&[u8]; 2] = [b"g2_blend_spl_sur", b"g2blnsur"];
let (start, name_len) = names.into_iter().find_map(|name| {
record_bytes
.windows(name.len() + 3)
.position(|window| {
window[0] == 0x0f
&& matches!(window[1], 0x0d | 0x0e)
&& usize::from(window[2]) == name.len()
&& &window[3..] == name
})
.map(|start| (start, name.len()))
})?;
let span = subtype_span(record_bytes, start, int_width)?;
let mut position = name_len + 3;
let first = decode_g2_side(span, &mut position, int_width)?;
let singularity = take_tagged_int(span, &mut position, 0x15, int_width)?;
let first_shape = if matches!(span.get(position), Some(0x0d | 0x0e)) {
let saved = position;
if take_native_ident(span, &mut position).as_deref() == Some("nullbs") {
EmbeddedG2FirstShape::Full {
surface: None,
tolerance: None,
}
} else {
position = saved;
let surface = decode_surface_block(span, position, int_width)?;
position = surface.end;
EmbeddedG2FirstShape::Full {
surface: Some(surface.surface),
tolerance: Some(take_f64(span, &mut position)? * LEN_TO_MM),
}
}
} else {
let mut coefficients = [0.0; 9];
for coefficient in &mut coefficients {
*coefficient = take_f64(span, &mut position)?;
}
let tolerance = take_f64(span, &mut position)? * LEN_TO_MM;
let extension = (!matches!(span.get(position), Some(0x07..=0x09 | 0x0d | 0x0e)))
.then(|| take_bridge_token(span, &mut position, int_width))
.flatten();
let pcurve = decode_nullable_embedded_pcurve(span, &mut position, int_width)?;
EmbeddedG2FirstShape::None {
coefficients,
tolerance,
extension,
pcurve,
}
};
let second = decode_g2_side(span, &mut position, int_width)?;
let second_exact = decode_surface_block(span, position, int_width)?;
position = second_exact.end;
let center = decode_curve_block(span, position, int_width)?;
position = center.end;
let center_parameters = [
take_f64(span, &mut position)?,
take_f64(span, &mut position)?,
];
let center_flag = take_tagged_int(span, &mut position, 0x04, int_width)?;
let parameter_ranges = [
[
take_f64(span, &mut position)?,
take_f64(span, &mut position)?,
],
[
take_f64(span, &mut position)?,
take_f64(span, &mut position)?,
],
];
let mut trailing_parameters = [0.0; 4];
for parameter in &mut trailing_parameters {
*parameter = take_f64(span, &mut position)?;
}
let cache = decode_surface_block(span, position, int_width)?;
let cache_fit_tolerance = (span.get(cache.end) == Some(&0x06))
.then(|| read_f64(span, cache.end + 1).map(|value| value * LEN_TO_MM))
.flatten();
position = cache.end + 9;
let discontinuities = [
take_float_array(span, &mut position, int_width)?,
take_float_array(span, &mut position, int_width)?,
take_float_array(span, &mut position, int_width)?,
];
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::G2Blend(Box::new(EmbeddedG2Blend {
first,
singularity,
first_shape,
second,
second_exact_surface: second_exact.surface,
center_curve: center.curve,
center_parameters,
center_flag,
parameter_ranges,
trailing_parameters,
discontinuities,
})),
cache_fit_tolerance,
})
}
pub(crate) struct EmbeddedLoftProfileData {
pub(crate) surface: SurfaceGeometry,
pub(crate) pcurve: Option<NurbsPcurve>,
pub(crate) first_flag: bool,
pub(crate) asm_extension: i64,
pub(crate) subdata: cadmpeg_ir::geometry::LoftSubdata,
pub(crate) direction: Option<Vector3>,
}
pub(crate) struct EmbeddedLoftProfileMember {
pub(crate) type_code: i64,
pub(crate) curve: NurbsCurve,
pub(crate) data: EmbeddedLoftProfileData,
}
pub(crate) struct EmbeddedLoftPath {
pub(crate) curve: NurbsCurve,
pub(crate) auxiliaries: Vec<NurbsCurve>,
pub(crate) flag: i64,
}
pub(crate) struct EmbeddedLoftSectionEntry {
pub(crate) parameter: f64,
pub(crate) profile: Vec<EmbeddedLoftProfileMember>,
pub(crate) path: EmbeddedLoftPath,
}
pub struct EmbeddedLoft {
pub(crate) sections: [Vec<EmbeddedLoftSectionEntry>; 2],
pub(crate) parameter_ranges: [[f64; 2]; 2],
pub(crate) closures: [i64; 2],
pub(crate) singularities: [i64; 2],
pub(crate) mode: i64,
pub(crate) bridge: Vec<cadmpeg_ir::geometry::LoftBridgeToken>,
}
pub(crate) struct EmbeddedCompoundLoftScale {
pub(crate) members: Vec<EmbeddedLoftProfileMember>,
pub(crate) path: NurbsCurve,
pub(crate) auxiliaries: Vec<NurbsCurve>,
pub(crate) tail: [i64; 2],
}
pub(crate) enum EmbeddedCompoundLoftDirection {
Vector(Vector3),
Curve(NurbsCurve),
}
pub(crate) enum EmbeddedCompoundLoftTail {
Six {
flags: [bool; 2],
scale: Option<Box<EmbeddedCompoundLoftScale>>,
selector: i64,
direction: Vector3,
parameter_range: [f64; 2],
curve: NurbsCurve,
},
Seven {
first_flag: bool,
first_scale: Option<Box<EmbeddedCompoundLoftScale>>,
second_flag: bool,
second_scale: Option<Box<EmbeddedCompoundLoftScale>>,
selector: i64,
direction: Vector3,
trailing_flags: [bool; 2],
},
Zero {
flags: [bool; 2],
selector: i64,
direction: EmbeddedCompoundLoftDirection,
trailing_flags: [bool; 2],
},
}
pub struct EmbeddedCompoundLoft {
pub(crate) scales: Box<[Option<EmbeddedCompoundLoftScale>; 4]>,
pub(crate) fifth_scale: Option<Box<EmbeddedCompoundLoftScale>>,
pub(crate) flags: [bool; 2],
pub(crate) tail: EmbeddedCompoundLoftTail,
}
#[allow(clippy::option_option)] fn decode_compound_loft_scale(
bytes: &[u8],
position: &mut usize,
int_width: usize,
) -> Option<Option<EmbeddedCompoundLoftScale>> {
if matches!(bytes.get(*position), Some(0x0a | 0x0b)) {
return Some(None);
}
let count = usize::try_from(take_tagged_int(bytes, position, 0x04, int_width)?).ok()?;
if count > 100_000 {
return None;
}
let mut members = Vec::with_capacity(count);
for _ in 0..count {
let type_code = take_tagged_int(bytes, position, 0x04, int_width)?;
let curve = decode_curve_block(bytes, *position, int_width)?;
*position = curve.end;
let data = decode_loft_profile_data(bytes, position, int_width)?;
members.push(EmbeddedLoftProfileMember {
type_code,
curve: curve.curve,
data,
});
}
let path = decode_curve_block(bytes, *position, int_width)?;
*position = path.end;
let auxiliary_count =
usize::try_from(take_tagged_int(bytes, position, 0x04, int_width)?).ok()?;
if auxiliary_count > 100_000 {
return None;
}
let mut auxiliaries = Vec::with_capacity(auxiliary_count);
for _ in 0..auxiliary_count {
let curve = decode_curve_block(bytes, *position, int_width)?;
*position = curve.end;
auxiliaries.push(curve.curve);
}
let tail = [
take_tagged_int(bytes, position, 0x04, int_width)?,
take_tagged_int(bytes, position, 0x04, int_width)?,
];
Some(Some(EmbeddedCompoundLoftScale {
members,
path: path.curve,
auxiliaries,
tail,
}))
}
fn decode_loft_subdata(
bytes: &[u8],
position: &mut usize,
int_width: usize,
) -> Option<cadmpeg_ir::geometry::LoftSubdata> {
use cadmpeg_ir::geometry::{LoftSubdata, LoftSubdataRow};
let type_code = take_tagged_int(bytes, position, 0x04, int_width)?;
let row_count = take_tagged_int(bytes, position, 0x04, int_width)?;
let column_count = take_tagged_int(bytes, position, 0x04, int_width)?;
let rows_to_read = if type_code == 211 {
1
} else {
usize::try_from(row_count).ok()?
};
let columns_to_read = usize::try_from(column_count).ok()?;
let mut rows = Vec::with_capacity(rows_to_read);
for _ in 0..rows_to_read {
let parameters = [take_f64(bytes, position)?, take_f64(bytes, position)?];
let mut columns = Vec::new();
if type_code != 211 {
columns.reserve(columns_to_read);
for _ in 0..columns_to_read {
columns.push([take_f64(bytes, position)?, take_f64(bytes, position)?]);
}
}
rows.push(LoftSubdataRow {
parameters,
columns,
});
}
Some(LoftSubdata {
type_code,
row_count,
column_count,
rows,
})
}
fn decode_loft_profile_data(
bytes: &[u8],
position: &mut usize,
int_width: usize,
) -> Option<EmbeddedLoftProfileData> {
let surface = decode_embedded_surface(bytes, position, int_width)?;
let saved = *position;
let pcurve = if take_native_ident(bytes, position).as_deref() == Some("nullbs") {
None
} else {
*position = saved;
let (pcurve, end) = decode_pcurve_block_with_end(bytes, *position, int_width)?;
*position = end;
Some(pcurve)
};
let first_flag = take_bool(bytes, position)?;
let asm_extension = take_tagged_int(bytes, position, 0x04, int_width)?;
let subdata = decode_loft_subdata(bytes, position, int_width)?;
let direction = if take_bool(bytes, position)? {
let value = take_native_vec3(bytes, position, 0x14)?;
Some(Vector3::new(value[0], value[1], value[2]))
} else {
None
};
Some(EmbeddedLoftProfileData {
surface,
pcurve,
first_flag,
asm_extension,
subdata,
direction,
})
}
fn decode_loft_section(
bytes: &[u8],
position: &mut usize,
int_width: usize,
) -> Option<Vec<EmbeddedLoftSectionEntry>> {
let count = usize::try_from(take_tagged_int(bytes, position, 0x04, int_width)?).ok()?;
let mut entries = Vec::with_capacity(count);
for _ in 0..count {
let parameter = take_f64(bytes, position)?;
let member_count =
usize::try_from(take_tagged_int(bytes, position, 0x04, int_width)?).ok()?;
let mut profile = Vec::with_capacity(member_count);
for _ in 0..member_count {
let type_code = take_tagged_int(bytes, position, 0x04, int_width)?;
let curve = decode_curve_block(bytes, *position, int_width)?;
*position = curve.end;
let data = decode_loft_profile_data(bytes, position, int_width)?;
profile.push(EmbeddedLoftProfileMember {
type_code,
curve: curve.curve,
data,
});
}
let curve = decode_curve_block(bytes, *position, int_width)?;
*position = curve.end;
let auxiliary_count =
usize::try_from(take_tagged_int(bytes, position, 0x04, int_width)?).ok()?;
let mut auxiliaries = Vec::with_capacity(auxiliary_count);
for _ in 0..auxiliary_count {
let auxiliary = decode_curve_block(bytes, *position, int_width)?;
*position = auxiliary.end;
auxiliaries.push(auxiliary.curve);
}
let flag = take_tagged_int(bytes, position, 0x04, int_width)?;
entries.push(EmbeddedLoftSectionEntry {
parameter,
profile,
path: EmbeddedLoftPath {
curve: curve.curve,
auxiliaries,
flag,
},
});
}
Some(entries)
}
fn decode_loft_spl_sur(record_bytes: &[u8], int_width: usize) -> Option<DecodedProceduralSurface> {
use cadmpeg_ir::geometry::LoftBridgeToken;
let names: [&[u8]; 2] = [b"loft_spl_sur", b"loftsur"];
let (start, name_len) = names.into_iter().find_map(|name| {
record_bytes
.windows(name.len() + 3)
.position(|window| {
window[0] == 0x0f
&& matches!(window[1], 0x0d | 0x0e)
&& usize::from(window[2]) == name.len()
&& &window[3..] == name
})
.map(|start| (start, name.len()))
})?;
let span = subtype_span(record_bytes, start, int_width)?;
let mut position = name_len + 3;
let sections = [
decode_loft_section(span, &mut position, int_width)?,
decode_loft_section(span, &mut position, int_width)?,
];
let parameter_ranges = [
[
take_f64(span, &mut position)?,
take_f64(span, &mut position)?,
],
[
take_f64(span, &mut position)?,
take_f64(span, &mut position)?,
],
];
let closures = [
take_tagged_int(span, &mut position, 0x15, int_width)?,
take_tagged_int(span, &mut position, 0x15, int_width)?,
];
let singularities = [
take_tagged_int(span, &mut position, 0x15, int_width)?,
take_tagged_int(span, &mut position, 0x15, int_width)?,
];
let mode = take_tagged_int(span, &mut position, 0x04, int_width)?;
let (cache_at, cache) = marker_positions(span)
.into_iter()
.filter_map(|at| decode_surface_block(span, at, int_width).map(|cache| (at, cache)))
.next_back()?;
let mut bridge = Vec::new();
while position < cache_at {
match *span.get(position)? {
0x0a | 0x0b => bridge.push(LoftBridgeToken::Boolean(take_bool(span, &mut position)?)),
0x04 => bridge.push(LoftBridgeToken::Integer(take_tagged_int(
span,
&mut position,
0x04,
int_width,
)?)),
0x06 => bridge.push(LoftBridgeToken::Double(take_f64(span, &mut position)?)),
0x15 => bridge.push(LoftBridgeToken::Enum(take_tagged_int(
span,
&mut position,
0x15,
int_width,
)?)),
0x07..=0x09 => {
bridge.push(LoftBridgeToken::Text(take_native_string(
span,
&mut position,
)?));
}
_ => return None,
}
}
let cache_fit_tolerance = (span.get(cache.end) == Some(&0x06))
.then(|| read_f64(span, cache.end + 1).map(|value| value * LEN_TO_MM))
.flatten();
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Loft(EmbeddedLoft {
sections,
parameter_ranges,
closures,
singularities,
mode,
bridge,
}),
cache_fit_tolerance,
})
}
fn decode_compound_loft_spl_sur(
record_bytes: &[u8],
int_width: usize,
) -> Option<DecodedProceduralSurface> {
let name = b"cl_loft_spl_sur";
let start = record_bytes.windows(name.len() + 3).position(|window| {
window[0] == 0x0f
&& matches!(window[1], 0x0d | 0x0e)
&& usize::from(window[2]) == name.len()
&& &window[3..] == name
})?;
let span = subtype_span(record_bytes, start, int_width)?;
let mut position = name.len() + 3;
let cache = decode_surface_block(span, position, int_width)?;
position = cache.end;
let cache_fit_tolerance = Some(take_f64(span, &mut position)? * LEN_TO_MM);
let scales = Box::new([
decode_compound_loft_scale(span, &mut position, int_width)?,
decode_compound_loft_scale(span, &mut position, int_width)?,
decode_compound_loft_scale(span, &mut position, int_width)?,
decode_compound_loft_scale(span, &mut position, int_width)?,
]);
let fifth_scale = if span.get(position) == Some(&0x04) {
decode_compound_loft_scale(span, &mut position, int_width)?.map(Box::new)
} else {
None
};
let flags = [
take_bool(span, &mut position)?,
take_bool(span, &mut position)?,
];
let kind = take_tagged_int(span, &mut position, 0x04, int_width)?;
let tail = match kind {
6 => {
let tail_flags = [
take_bool(span, &mut position)?,
take_bool(span, &mut position)?,
];
let scale = decode_compound_loft_scale(span, &mut position, int_width)?.map(Box::new);
let selector = take_tagged_int(span, &mut position, 0x04, int_width)?;
let direction = take_native_vec3(span, &mut position, 0x14)?;
let parameter_range = [
take_range_value(span, &mut position)?,
take_range_value(span, &mut position)?,
];
let curve = decode_curve_block(span, position, int_width)?;
EmbeddedCompoundLoftTail::Six {
flags: tail_flags,
scale,
selector,
direction: Vector3::new(direction[0], direction[1], direction[2]),
parameter_range,
curve: curve.curve,
}
}
7 => {
let first_flag = take_bool(span, &mut position)?;
let first_scale =
decode_compound_loft_scale(span, &mut position, int_width)?.map(Box::new);
let second_flag = take_bool(span, &mut position)?;
let second_scale =
decode_compound_loft_scale(span, &mut position, int_width)?.map(Box::new);
let selector = take_tagged_int(span, &mut position, 0x04, int_width)?;
let direction = take_native_vec3(span, &mut position, 0x14)?;
let trailing_flags = [
take_bool(span, &mut position)?,
take_bool(span, &mut position)?,
];
EmbeddedCompoundLoftTail::Seven {
first_flag,
first_scale,
second_flag,
second_scale,
selector,
direction: Vector3::new(direction[0], direction[1], direction[2]),
trailing_flags,
}
}
0 => {
let tail_flags = [
take_bool(span, &mut position)?,
take_bool(span, &mut position)?,
];
let selector = take_tagged_int(span, &mut position, 0x04, int_width)?;
let direction = if selector == 0 {
let value = take_native_vec3(span, &mut position, 0x14)?;
EmbeddedCompoundLoftDirection::Vector(Vector3::new(value[0], value[1], value[2]))
} else {
let curve = decode_curve_block(span, position, int_width)?;
position = curve.end;
EmbeddedCompoundLoftDirection::Curve(curve.curve)
};
let trailing_flags = [
take_bool(span, &mut position)?,
take_bool(span, &mut position)?,
];
EmbeddedCompoundLoftTail::Zero {
flags: tail_flags,
selector,
direction,
trailing_flags,
}
}
_ => return None,
};
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::CompoundLoft(Box::new(
EmbeddedCompoundLoft {
scales,
fifth_scale,
flags,
tail,
},
)),
cache_fit_tolerance,
})
}
fn decode_taper_spl_sur(record_bytes: &[u8], int_width: usize) -> Option<DecodedProceduralSurface> {
use cadmpeg_ir::geometry::TaperSurfaceKind;
let names: &[(&[u8], u8)] = &[
(b"taper_spl_sur", 0),
(b"ortho_spl_sur", 1),
(b"orthosur", 1),
(b"edge_tpr_spl_sur", 2),
(b"shadow_tpr_spl_sur", 3),
(b"shadowtapersur", 3),
(b"ruled_tpr_spl_sur", 4),
(b"ruledtapersur", 4),
(b"swept_tpr_spl_sur", 5),
(b"swepttapersur", 5),
];
let (start, name_len, kind) = names.iter().find_map(|(name, kind)| {
record_bytes
.windows(name.len() + 3)
.position(|window| {
window[0] == 0x0f
&& matches!(window[1], 0x0d | 0x0e)
&& usize::from(window[2]) == name.len()
&& &window[3..] == *name
})
.map(|start| (start, name.len(), *kind))
})?;
let span = subtype_span(record_bytes, start, int_width)?;
let mut position = name_len + 3;
let support = decode_embedded_surface(span, &mut position, int_width)?;
let reference = decode_curve_block(span, position, int_width)?;
position = reference.end;
let saved = position;
let pcurve = if take_native_ident(span, &mut position).as_deref() == Some("nullbs") {
None
} else {
position = saved;
let (pcurve, end) = decode_pcurve_block_with_end(span, position, int_width)?;
position = end;
Some(pcurve)
};
let parameter = take_f64(span, &mut position)?;
let cache = marker_positions(span)
.into_iter()
.filter_map(|at| decode_surface_block(span, at, int_width))
.next_back()?;
let cache_fit_tolerance = (span.get(cache.end) == Some(&0x06))
.then(|| read_f64(span, cache.end + 1).map(|value| value * LEN_TO_MM))
.flatten();
position = cache.end + 9;
let take_draft = |position: &mut usize| {
let draft = take_native_vec3(span, position, 0x14)?;
Some(Vector3::new(draft[0], draft[1], draft[2]))
};
let taper = match kind {
0 => TaperSurfaceKind::Standard,
1 => TaperSurfaceKind::Orthogonal {
sense: take_bool(span, &mut position)?,
},
2 => TaperSurfaceKind::Edge {
draft: take_draft(&mut position)?,
},
3 => TaperSurfaceKind::Shadow {
draft: take_draft(&mut position)?,
sine: take_f64(span, &mut position)?,
cosine: take_f64(span, &mut position)?,
},
4 => TaperSurfaceKind::Ruled {
draft: take_draft(&mut position)?,
sine: take_f64(span, &mut position)?,
cosine: take_f64(span, &mut position)?,
factor: take_f64(span, &mut position)?,
},
5 => TaperSurfaceKind::Swept {
draft: take_draft(&mut position)?,
sine: take_f64(span, &mut position)?,
cosine: take_f64(span, &mut position)?,
},
_ => return None,
};
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Taper {
support,
reference: reference.curve,
pcurve,
parameter,
taper,
},
cache_fit_tolerance,
})
}
fn decode_comp_spl_sur(record_bytes: &[u8], int_width: usize) -> Option<DecodedProceduralSurface> {
let name = b"comp_spl_sur";
let start = record_bytes.windows(name.len() + 3).position(|window| {
window[0] == 0x0f
&& matches!(window[1], 0x0d | 0x0e)
&& usize::from(window[2]) == name.len()
&& &window[3..] == name
})?;
let span = subtype_span(record_bytes, start, int_width)?;
let cache = marker_positions(span)
.into_iter()
.find_map(|at| decode_surface_block(span, at, int_width))?;
let cache_fit_tolerance = (span.get(cache.end) == Some(&0x06))
.then(|| read_f64(span, cache.end + 1).map(|value| value * LEN_TO_MM))
.flatten();
let mut position = cache.end + 9;
let parameters = take_float_array(span, &mut position, int_width)?;
let mut components = Vec::with_capacity(parameters.len());
for _ in 0..parameters.len() {
components.push(decode_embedded_surface(span, &mut position, int_width)?);
}
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Compound {
parameters,
components,
},
cache_fit_tolerance,
})
}
fn decode_off_spl_sur(record_bytes: &[u8], int_width: usize) -> Option<DecodedProceduralSurface> {
let names: [&[u8]; 2] = [b"off_spl_sur", b"offsur"];
let (start, name_len, modern) = names.into_iter().find_map(|name| {
record_bytes
.windows(name.len() + 3)
.position(|window| {
window[0] == 0x0f
&& matches!(window[1], 0x0d | 0x0e)
&& usize::from(window[2]) == name.len()
&& &window[3..] == name
})
.map(|start| (start, name.len(), name == b"off_spl_sur"))
})?;
let span = subtype_span(record_bytes, start, int_width)?;
let mut position = name_len + 3;
let support = decode_embedded_surface(span, &mut position, int_width)?;
let distance = take_f64(span, &mut position)? * LEN_TO_MM;
let u_sense = take_tagged_int(span, &mut position, 0x15, int_width)?;
let v_sense = take_tagged_int(span, &mut position, 0x15, int_width)?;
let mut extension_flags = Vec::new();
if modern {
let first = take_bool(span, &mut position)?;
extension_flags.push(first);
if first {
extension_flags.push(take_bool(span, &mut position)?);
if matches!(span.get(position), Some(0x0a | 0x0b)) {
extension_flags.push(take_bool(span, &mut position)?);
}
}
}
let cache = marker_positions(span)
.into_iter()
.filter_map(|at| decode_surface_block(span, at, int_width))
.next_back()?;
let cache_fit_tolerance = (span.get(cache.end) == Some(&0x06))
.then(|| read_f64(span, cache.end + 1).map(|value| value * LEN_TO_MM))
.flatten();
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Offset {
support,
distance,
u_sense,
v_sense,
extension_flags,
},
cache_fit_tolerance,
})
}
fn decode_rot_spl_sur(record_bytes: &[u8], int_width: usize) -> Option<DecodedProceduralSurface> {
let names: [&[u8]; 2] = [b"rot_spl_sur", b"rotsur"];
let start = names.into_iter().find_map(|name| {
record_bytes.windows(name.len() + 3).position(|window| {
window[0] == 0x0f
&& matches!(window[1], 0x0d | 0x0e)
&& usize::from(window[2]) == name.len()
&& &window[3..] == name
})
})?;
let span = subtype_span(record_bytes, start, int_width)?;
let directrix = marker_positions(span)
.into_iter()
.find_map(|at| decode_curve_block(span, at, int_width))?;
let parameter_interval = [
*directrix.curve.knots.first()?,
*directrix.curve.knots.last()?,
];
let mut position = directrix.end;
let origin = take_native_vec3(span, &mut position, 0x13)?;
let axis_origin = Point3::new(
origin[0] * LEN_TO_MM,
origin[1] * LEN_TO_MM,
origin[2] * LEN_TO_MM,
);
let axis = take_native_vec3(span, &mut position, 0x14)?;
let axis_direction = normalized(axis)?;
let cache = marker_positions(span)
.into_iter()
.filter_map(|at| decode_surface_block(span, at, int_width))
.next_back()?;
let angular_interval = [
*cache.surface.v_knots.first()?,
*cache.surface.v_knots.last()?,
];
let cache_fit_tolerance = (span.get(cache.end) == Some(&0x06))
.then(|| read_f64(span, cache.end + 1).map(|value| value * LEN_TO_MM))
.flatten();
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Revolution {
directrix: directrix.curve,
axis_origin,
axis_direction,
angular_interval,
parameter_interval,
},
cache_fit_tolerance,
})
}
fn decode_sum_spl_sur(record_bytes: &[u8], int_width: usize) -> Option<DecodedProceduralSurface> {
let names: [&[u8]; 2] = [b"sum_spl_sur", b"sumsur"];
let start = names.into_iter().find_map(|name| {
record_bytes.windows(name.len() + 3).position(|window| {
window[0] == 0x0f
&& matches!(window[1], 0x0d | 0x0e)
&& usize::from(window[2]) == name.len()
&& &window[3..] == name
})
})?;
let span = subtype_span(record_bytes, start, int_width)?;
let mut decoded_curves = marker_positions(span)
.into_iter()
.filter_map(|at| decode_curve_block(span, at, int_width));
let first = decoded_curves.next()?;
let second = decoded_curves.next()?;
let mut position = second.end;
let origin = take_native_vec3(span, &mut position, 0x13)?;
let basepoint = Vector3::new(
origin[0] * LEN_TO_MM,
origin[1] * LEN_TO_MM,
origin[2] * LEN_TO_MM,
);
let cache = marker_positions(span)
.into_iter()
.filter_map(|at| decode_surface_block(span, at, int_width))
.next_back()?;
let cache_fit_tolerance = (span.get(cache.end) == Some(&0x06))
.then(|| read_f64(span, cache.end + 1).map(|value| value * LEN_TO_MM))
.flatten();
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Sum {
first: first.curve,
second: second.curve,
basepoint,
},
cache_fit_tolerance,
})
}
fn decode_ruled_spl_sur(record_bytes: &[u8], int_width: usize) -> Option<DecodedProceduralSurface> {
let names: [&[u8]; 2] = [b"rule_sur", b"rulesur"];
let start = names.into_iter().find_map(|name| {
record_bytes.windows(name.len() + 3).position(|window| {
window[0] == 0x0f
&& matches!(window[1], 0x0d | 0x0e)
&& usize::from(window[2]) == name.len()
&& &window[3..] == name
})
})?;
let span = subtype_span(record_bytes, start, int_width)?;
let mut curves = marker_positions(span)
.into_iter()
.filter_map(|at| decode_curve_block(span, at, int_width).map(|decoded| decoded.curve));
let first = curves.next()?;
let second = curves.next()?;
let cache = marker_positions(span)
.into_iter()
.filter_map(|at| decode_surface_block(span, at, int_width))
.next_back()?;
let cache_fit_tolerance = (span.get(cache.end) == Some(&0x06))
.then(|| read_f64(span, cache.end + 1).map(|value| value * LEN_TO_MM))
.flatten();
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Ruled { first, second },
cache_fit_tolerance,
})
}
fn decode_exact_spl_sur(record_bytes: &[u8], int_width: usize) -> Option<DecodedProceduralSurface> {
let names: [&[u8]; 2] = [b"exact_spl_sur", b"exactsur"];
let (start, name) = names.into_iter().find_map(|name| {
record_bytes
.windows(name.len() + 3)
.position(|window| {
window[0] == 0x0f
&& matches!(window[1], 0x0d | 0x0e)
&& usize::from(window[2]) == name.len()
&& &window[3..] == name
})
.map(|start| (start, name))
})?;
let span = subtype_span(record_bytes, start, int_width)?;
let cache = marker_positions(span)
.into_iter()
.filter_map(|at| decode_surface_block(span, at, int_width))
.next_back()?;
let cache_fit_tolerance = (span.get(cache.end) == Some(&0x06))
.then(|| read_f64(span, cache.end + 1).map(|value| value * LEN_TO_MM))
.flatten();
let mut position = cache.end + 9;
let parameter_ranges = [
[
take_range_value(span, &mut position)?,
take_range_value(span, &mut position)?,
],
[
take_range_value(span, &mut position)?,
take_range_value(span, &mut position)?,
],
];
let extension = take_tagged_int(span, &mut position, 0x04, int_width)?;
let _ = name;
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Exact {
parameter_ranges,
extension,
},
cache_fit_tolerance,
})
}
pub fn decode_cyl_spl_sur(record_bytes: &[u8]) -> Option<DecodedProceduralSurface> {
INT_WIDTHS
.into_iter()
.find_map(|int_width| decode_cyl_spl_sur_at(record_bytes, int_width))
}
fn decode_cyl_spl_sur_at(
record_bytes: &[u8],
int_width: usize,
) -> Option<DecodedProceduralSurface> {
let marker = b"\x0f\x0d\x0bcyl_spl_sur";
let start = record_bytes
.windows(marker.len())
.position(|w| w == marker)?;
let span = subtype_span(record_bytes, start, int_width)?;
let directrix = decode_curve_cache_at(span, int_width)?;
let mut doubles = Vec::new();
let mut direction = None;
let mut pos = marker.len();
while pos < span.len() {
match span[pos] {
0x06 if direction.is_none() => doubles.push(read_f64(span, pos + 1)?),
0x14 if direction.is_none() => {
direction = Some(Vector3::new(
read_f64(span, pos + 1)? * LEN_TO_MM,
read_f64(span, pos + 9)? * LEN_TO_MM,
read_f64(span, pos + 17)? * LEN_TO_MM,
));
}
_ => {}
}
pos = next_token(span, pos, int_width)?;
}
let _u_range = [*doubles.first()?, *doubles.get(1)?];
let decoded_cache = marker_positions(span)
.into_iter()
.filter_map(|at| decode_surface_block(span, at, int_width))
.next_back()?;
let _v_range = [
*decoded_cache.surface.v_knots.first()?,
*decoded_cache.surface.v_knots.last()?,
];
let cache_fit_tolerance = (span.get(decoded_cache.end) == Some(&0x06))
.then(|| read_f64(span, decoded_cache.end + 1).map(|v| v * LEN_TO_MM))
.flatten();
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Extrusion {
directrix,
direction: direction?,
},
cache_fit_tolerance,
})
}
fn decode_rolling_ball_side(
bytes: &[u8],
position: &mut usize,
int_width: usize,
) -> Option<EmbeddedRollingBallSide> {
let label = take_native_string(bytes, position)?;
let saved = *position;
let surface = if take_native_ident(bytes, position).as_deref() == Some("null_surface") {
None
} else {
*position = saved;
Some(decode_embedded_surface(bytes, position, int_width)?)
};
let curve = decode_curve_block(bytes, *position, int_width)?;
*position = curve.end;
let pcurve = decode_nullable_embedded_pcurve(bytes, position, int_width)?;
let location = take_native_vec3(bytes, position, 0x13)?;
let secondary_pcurve = decode_nullable_embedded_pcurve(bytes, position, int_width)?;
let saved = *position;
let exact_support = if matches!(
take_native_ident(bytes, position).as_deref(),
Some("nullbs" | "null_surface")
) {
None
} else {
*position = saved;
match decode_embedded_surface(bytes, position, int_width)? {
SurfaceGeometry::Nurbs(surface) => Some(surface),
_ => return None,
}
};
Some(EmbeddedRollingBallSide {
label,
surface,
curve: curve.curve,
pcurve,
location: Point3::new(
location[0] * LEN_TO_MM,
location[1] * LEN_TO_MM,
location[2] * LEN_TO_MM,
),
secondary_pcurve,
exact_support,
})
}
fn decode_rolling_ball_third_side(
bytes: &[u8],
position: &mut usize,
int_width: usize,
) -> Option<EmbeddedRollingBallThirdSide> {
let label = take_native_string(bytes, position)?;
let surface = decode_embedded_surface(bytes, position, int_width)?;
let curve = decode_curve_block(bytes, *position, int_width)?;
*position = curve.end;
let pcurve = decode_nullable_embedded_pcurve(bytes, position, int_width)?;
let direction = take_native_vec3(bytes, position, 0x14)?;
let secondary_pcurve = decode_nullable_embedded_pcurve(bytes, position, int_width)?;
let extension = take_tagged_int(bytes, position, 0x04, int_width)?;
let tertiary_pcurve = decode_nullable_embedded_pcurve(bytes, position, int_width)?;
let flag = take_bool(bytes, position)?;
Some(EmbeddedRollingBallThirdSide {
label,
surface,
curve: curve.curve,
pcurve,
direction: Vector3::new(direction[0], direction[1], direction[2]),
secondary_pcurve,
extension,
tertiary_pcurve,
flag,
})
}
fn decode_variable_blend_side(
bytes: &[u8],
position: &mut usize,
int_width: usize,
) -> Option<EmbeddedVariableBlendSide> {
let label = take_native_string(bytes, position)?;
let surface = decode_embedded_surface(bytes, position, int_width)?;
let curve = decode_curve_block(bytes, *position, int_width)?;
*position = curve.end;
let pcurve = decode_nullable_embedded_pcurve(bytes, position, int_width)?;
let location = take_native_vec3(bytes, position, 0x13)?;
let secondary_pcurve = decode_nullable_embedded_pcurve(bytes, position, int_width)?;
let scalar = take_f64(bytes, position)?;
let tertiary_pcurve = decode_nullable_embedded_pcurve(bytes, position, int_width)?;
Some(EmbeddedVariableBlendSide {
label,
surface,
curve: curve.curve,
pcurve,
location: Point3::new(
location[0] * LEN_TO_MM,
location[1] * LEN_TO_MM,
location[2] * LEN_TO_MM,
),
secondary_pcurve,
scalar,
tertiary_pcurve,
})
}
fn take_blend_value_name(bytes: &[u8], position: &mut usize) -> Option<String> {
let saved = *position;
if let Some(value) = take_native_string(bytes, position) {
return Some(value);
}
*position = saved;
take_native_ident(bytes, position)
}
fn decode_variable_blend_value(
bytes: &[u8],
position: &mut usize,
int_width: usize,
modern: bool,
depth: usize,
) -> Option<cadmpeg_ir::geometry::VariableBlendValue> {
use cadmpeg_ir::geometry::{
LoftBridgeToken, VariableBlendInterpolationPoint, VariableBlendValue,
VariableBlendValuePayload,
};
if depth > 32 {
return None;
}
let name = take_blend_value_name(bytes, position)?;
let modern_flag = if modern {
take_bool(bytes, position)?
} else {
false
};
let discriminator = if bytes.get(*position) == Some(&0x04) {
take_tagged_int(bytes, position, 0x04, int_width)?
} else {
1
};
let calibrated = take_tagged_int(bytes, position, 0x15, int_width)?;
let payload = match name.as_str() {
"two_ends" => VariableBlendValuePayload::TwoEnds {
parameters: [take_f64(bytes, position)?, take_f64(bytes, position)?],
radii: [
take_f64(bytes, position)? * LEN_TO_MM,
take_f64(bytes, position)? * LEN_TO_MM,
],
},
"edge_offset" if discriminator == 0 => VariableBlendValuePayload::EdgeOffset {
scalars: vec![take_f64(bytes, position)?, take_f64(bytes, position)?],
lengths: vec![take_f64(bytes, position)? * LEN_TO_MM],
},
"edge_offset" if discriminator == 1 => VariableBlendValuePayload::EdgeOffset {
scalars: vec![take_f64(bytes, position)?],
lengths: vec![
take_f64(bytes, position)? * LEN_TO_MM,
take_f64(bytes, position)? * LEN_TO_MM,
],
},
"functional" => {
let parameter = take_f64(bytes, position)?;
let radius = take_f64(bytes, position)? * LEN_TO_MM;
let (function, end) = decode_pcurve_block_with_end(bytes, *position, int_width)?;
*position = end;
let terminal = if bytes.get(*position) == Some(&0x06) {
LoftBridgeToken::Double(take_f64(bytes, position)?)
} else {
LoftBridgeToken::Text(take_blend_value_name(bytes, position)?)
};
VariableBlendValuePayload::Functional {
parameter,
radius,
function: PcurveGeometry::Nurbs {
degree: function.degree,
knots: function.knots,
control_points: function.control_points,
weights: function.weights,
periodic: function.periodic,
},
terminal,
}
}
"const" => VariableBlendValuePayload::Constant {
parameters: [take_f64(bytes, position)?, take_f64(bytes, position)?],
radius: take_f64(bytes, position)? * LEN_TO_MM,
variable_chamfer: take_tagged_int(bytes, position, 0x15, int_width)?,
chamfer_type: take_tagged_int(bytes, position, 0x15, int_width)?,
nested: Box::new(decode_variable_blend_value(
bytes,
position,
int_width,
modern,
depth + 1,
)?),
},
"interp" => {
let parameter = take_f64(bytes, position)?;
let radius = take_f64(bytes, position)? * LEN_TO_MM;
let (function, end) = decode_pcurve_block_with_end(bytes, *position, int_width)?;
*position = end;
let enum_count = take_tagged_int(bytes, position, 0x04, int_width)?;
let count = usize::try_from(take_tagged_int(bytes, position, 0x04, int_width)?).ok()?;
if count > 100_000 {
return None;
}
let mut points = Vec::with_capacity(count);
for _ in 0..count {
let parameter = take_f64(bytes, position)?;
let radius = take_f64(bytes, position)? * LEN_TO_MM;
let tangents = [take_f64(bytes, position)?, take_f64(bytes, position)?];
let location = take_native_vec3(bytes, position, 0x13)?;
let normal = take_native_vec3(bytes, position, 0x14)?;
points.push(VariableBlendInterpolationPoint {
parameter,
radius,
tangents,
location: Point3::new(
location[0] * LEN_TO_MM,
location[1] * LEN_TO_MM,
location[2] * LEN_TO_MM,
),
normal: Vector3::new(normal[0], normal[1], normal[2]),
});
}
let tail = if take_tagged_int(bytes, position, 0x04, int_width)? != 0 {
Some([take_f64(bytes, position)?, take_f64(bytes, position)?])
} else {
None
};
VariableBlendValuePayload::Interpolated {
parameter,
radius,
function: PcurveGeometry::Nurbs {
degree: function.degree,
knots: function.knots,
control_points: function.control_points,
weights: function.weights,
periodic: function.periodic,
},
enum_count,
points,
tail,
}
}
_ => return None,
};
Some(VariableBlendValue {
name,
modern_flag,
discriminator,
calibrated,
payload,
})
}
#[cfg(test)]
mod variable_blend_value_tests {
use super::*;
use cadmpeg_ir::geometry::VariableBlendValuePayload;
fn text(bytes: &mut Vec<u8>, value: &str) {
bytes.push(0x07);
bytes.push(u8::try_from(value.len()).expect("generated text length"));
bytes.extend_from_slice(value.as_bytes());
}
fn integer(bytes: &mut Vec<u8>, tag: u8, value: i64) {
bytes.push(tag);
bytes.extend_from_slice(&value.to_le_bytes());
}
fn double(bytes: &mut Vec<u8>, value: f64) {
bytes.push(0x06);
bytes.extend_from_slice(&value.to_le_bytes());
}
fn two_ends(bytes: &mut Vec<u8>) {
text(bytes, "two_ends");
bytes.push(0x0a);
integer(bytes, 0x04, 7);
integer(bytes, 0x15, 3);
for value in [0.25, 0.75, 1.5, 2.5] {
double(bytes, value);
}
}
#[test]
fn decodes_generated_two_ends_and_recursive_const_values() {
let mut direct = Vec::new();
two_ends(&mut direct);
let mut position = 0;
let decoded = decode_variable_blend_value(&direct, &mut position, 8, true, 0)
.expect("generated two-ends value");
assert_eq!(position, direct.len());
assert!(decoded.modern_flag);
assert_eq!(decoded.discriminator, 7);
let VariableBlendValuePayload::TwoEnds { parameters, radii } = decoded.payload else {
panic!("expected two-ends payload")
};
assert_eq!(parameters, [0.25, 0.75]);
assert_eq!(radii, [15.0, 25.0]);
let mut recursive = Vec::new();
text(&mut recursive, "const");
recursive.push(0x0b);
integer(&mut recursive, 0x15, 4);
for value in [0.1, 0.9, 3.0] {
double(&mut recursive, value);
}
integer(&mut recursive, 0x15, 3);
integer(&mut recursive, 0x15, 2);
two_ends(&mut recursive);
let mut position = 0;
let decoded = decode_variable_blend_value(&recursive, &mut position, 8, true, 0)
.expect("generated recursive const value");
assert_eq!(position, recursive.len());
let VariableBlendValuePayload::Constant { radius, nested, .. } = decoded.payload else {
panic!("expected constant payload")
};
assert_eq!(radius, 30.0);
assert!(matches!(
nested.payload,
VariableBlendValuePayload::TwoEnds { .. }
));
}
}
fn decode_var_blend_spl_sur(
record_bytes: &[u8],
int_width: usize,
) -> Option<DecodedProceduralSurface> {
use cadmpeg_ir::geometry::{
LoftBridgeToken, VariableBlendChamfer, VariableBlendSingleRadiusTail,
};
let names: [&[u8]; 2] = [b"var_blend_spl_sur", b"srf_srf_v_bl_spl_sur"];
let (start, name_len) = names.into_iter().find_map(|name| {
record_bytes
.windows(name.len() + 3)
.position(|window| {
window[0] == 0x0f
&& matches!(window[1], 0x0d | 0x0e)
&& usize::from(window[2]) == name.len()
&& &window[3..] == name
})
.map(|start| (start, name.len()))
})?;
let span = subtype_span(record_bytes, start, int_width)?;
let mut position = name_len + 3;
let sides = Box::new([
decode_variable_blend_side(span, &mut position, int_width)?,
decode_variable_blend_side(span, &mut position, int_width)?,
]);
let primary = decode_curve_block(span, position, int_width)?;
position = primary.end;
let offsets = [
take_f64(span, &mut position)? * LEN_TO_MM,
take_f64(span, &mut position)? * LEN_TO_MM,
];
let radius_kind = take_tagged_int(span, &mut position, 0x15, int_width)?;
if !matches!(radius_kind, 0 | 1) {
return None;
}
let first_value = decode_variable_blend_value(span, &mut position, int_width, true, 0)?;
let second_value = if radius_kind == 1 {
Some(decode_variable_blend_value(
span,
&mut position,
int_width,
true,
0,
)?)
} else {
None
};
let chamfer = if radius_kind == 1
&& span.get(position) == Some(&0x15)
&& read_int(span, position + 1, int_width) == Some(3)
{
Some(Box::new(VariableBlendChamfer {
variable_chamfer: take_tagged_int(span, &mut position, 0x15, int_width)?,
chamfer_type: take_tagged_int(span, &mut position, 0x15, int_width)?,
value: decode_variable_blend_value(span, &mut position, int_width, true, 0)?,
}))
} else {
None
};
let single_radius_tail = if radius_kind == 0
&& span.get(position) == Some(&0x04)
&& matches!(read_int(span, position + 1, int_width), Some(1 | 7))
{
Some(VariableBlendSingleRadiusTail {
selector: LoftBridgeToken::Integer(take_tagged_int(
span,
&mut position,
0x04,
int_width,
)?),
parameters: [
take_f64(span, &mut position)?,
take_f64(span, &mut position)?,
],
})
} else {
None
};
let u_range = [
take_range_value(span, &mut position)?,
take_range_value(span, &mut position)?,
];
let v_range = [
take_range_value(span, &mut position)?,
take_range_value(span, &mut position)?,
];
let shape_prefix = take_tagged_int(span, &mut position, 0x04, int_width)?;
let shape_parameter = take_f64(span, &mut position)?;
let shape_length = take_f64(span, &mut position)? * LEN_TO_MM;
let shape_tail = take_tagged_int(span, &mut position, 0x04, int_width)?;
let cache = decode_surface_block(span, position, int_width)?;
position = cache.end;
let cache_fit_tolerance = Some(take_f64(span, &mut position)? * LEN_TO_MM);
let shape_extensions = [
take_tagged_int(span, &mut position, 0x04, int_width)?,
take_tagged_int(span, &mut position, 0x04, int_width)?,
take_tagged_int(span, &mut position, 0x04, int_width)?,
];
let secondary = decode_curve_block(span, position, int_width)?;
position = secondary.end;
let convexity = i64::from(take_bool(span, &mut position)?);
let render_blend = i64::from(take_bool(span, &mut position)?);
let post_range = [
take_range_value(span, &mut position)?,
take_range_value(span, &mut position)?,
];
let post = decode_curve_block(span, position, int_width)?;
position = post.end;
let post_pcurve = decode_nullable_embedded_pcurve(span, &mut position, int_width)?;
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::VariableBlend(Box::new(
EmbeddedVariableBlend {
sides,
primary_curve: primary.curve,
offsets,
radius_kind,
first_value,
second_value,
chamfer,
single_radius_tail,
u_range,
v_range,
shape_prefix,
shape_parameter,
shape_length,
shape_tail,
shape_extensions,
secondary_curve: secondary.curve,
convexity,
render_blend,
post_range,
post_curve: post.curve,
post_pcurve,
},
)),
cache_fit_tolerance,
})
}
fn decode_vertex_blend_boundary(
bytes: &[u8],
position: &mut usize,
int_width: usize,
) -> Option<EmbeddedVertexBlendBoundary> {
let kind = take_native_string(bytes, position)?;
let boundary_type = i64::from(take_bool(bytes, position)?);
let magic = take_native_vec3(bytes, position, 0x13)?;
let u_smoothing = i64::from(take_bool(bytes, position)?);
let v_smoothing = i64::from(take_bool(bytes, position)?);
let fullness = take_f64(bytes, position)?;
let geometry = match kind.as_str() {
"circle" => {
let curve = decode_curve_block(bytes, *position, int_width)?;
*position = curve.end;
let form = take_tagged_int(bytes, position, 0x15, int_width)?;
let twist_count = match form {
0 => 0,
1 => 1,
3 => 2,
_ => return None,
};
let mut twists = Vec::with_capacity(twist_count);
for _ in 0..twist_count {
let twist = take_native_vec3(bytes, position, 0x13)?;
twists.push(Point3::new(
twist[0] * LEN_TO_MM,
twist[1] * LEN_TO_MM,
twist[2] * LEN_TO_MM,
));
}
let parameters = [take_f64(bytes, position)?, take_f64(bytes, position)?];
let sense = i64::from(take_bool(bytes, position)?);
EmbeddedVertexBlendBoundaryGeometry::Circle {
curve: curve.curve,
form,
twists,
parameters,
sense,
}
}
"deg" => {
let location = take_native_vec3(bytes, position, 0x13)?;
let first = take_native_vec3(bytes, position, 0x14)?;
let second = take_native_vec3(bytes, position, 0x14)?;
EmbeddedVertexBlendBoundaryGeometry::Degenerate {
location: Point3::new(
location[0] * LEN_TO_MM,
location[1] * LEN_TO_MM,
location[2] * LEN_TO_MM,
),
normals: [
Vector3::new(first[0], first[1], first[2]),
Vector3::new(second[0], second[1], second[2]),
],
}
}
"pcurve" => {
let surface = decode_embedded_surface(bytes, position, int_width)?;
let pcurve = decode_nullable_embedded_pcurve(bytes, position, int_width)?;
let sense = i64::from(take_bool(bytes, position)?);
let fit_tolerance = take_f64(bytes, position)?;
EmbeddedVertexBlendBoundaryGeometry::Pcurve {
surface,
pcurve,
sense,
fit_tolerance,
}
}
"plane" => {
let normal = take_native_vec3(bytes, position, 0x14)?;
let parameters = [take_f64(bytes, position)?, take_f64(bytes, position)?];
let curve = decode_curve_block(bytes, *position, int_width)?;
*position = curve.end;
EmbeddedVertexBlendBoundaryGeometry::Plane {
normal: Vector3::new(normal[0], normal[1], normal[2]),
parameters,
curve: curve.curve,
}
}
_ => return None,
};
Some(EmbeddedVertexBlendBoundary {
boundary_type,
magic: Point3::new(
magic[0] * LEN_TO_MM,
magic[1] * LEN_TO_MM,
magic[2] * LEN_TO_MM,
),
u_smoothing,
v_smoothing,
fullness,
geometry,
})
}
fn decode_vertex_blend_spl_sur(
record_bytes: &[u8],
int_width: usize,
) -> Option<DecodedProceduralSurface> {
let names: [&[u8]; 2] = [b"VBL_SURF", b"vertexblendsur"];
let (start, name_len) = names.into_iter().find_map(|name| {
record_bytes
.windows(name.len() + 3)
.position(|window| {
window[0] == 0x0f
&& matches!(window[1], 0x0d | 0x0e)
&& usize::from(window[2]) == name.len()
&& &window[3..] == name
})
.map(|start| (start, name.len()))
})?;
let span = subtype_span(record_bytes, start, int_width)?;
let mut position = name_len + 3;
let count = usize::try_from(take_tagged_int(span, &mut position, 0x04, int_width)?).ok()?;
if count > 100_000 {
return None;
}
let mut boundaries = Vec::with_capacity(count);
for _ in 0..count {
boundaries.push(decode_vertex_blend_boundary(
span,
&mut position,
int_width,
)?);
}
let grid_size = take_tagged_int(span, &mut position, 0x04, int_width)?;
let fit_tolerance = take_f64(span, &mut position)? * LEN_TO_MM;
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::VertexBlend(Box::new(
EmbeddedVertexBlend {
boundaries,
grid_size,
fit_tolerance,
},
)),
cache_fit_tolerance: None,
})
}
fn decode_full_rb_blend_spl_sur(
record_bytes: &[u8],
int_width: usize,
) -> Option<DecodedProceduralSurface> {
let names: [(&[u8], bool); 6] = [
(b"rb_blend_spl_sur", false),
(b"rbblnsur", false),
(b"pipe_spl_sur", false),
(b"pipesur", false),
(b"sss_blend_spl_sur", true),
(b"sssblndsur", true),
];
let (start, name_len, has_third) = names.into_iter().find_map(|(name, has_third)| {
record_bytes
.windows(name.len() + 3)
.position(|window| {
window[0] == 0x0f
&& matches!(window[1], 0x0d | 0x0e)
&& usize::from(window[2]) == name.len()
&& &window[3..] == name
})
.map(|start| (start, name.len(), has_third))
})?;
let span = subtype_span(record_bytes, start, int_width)?;
let mut position = name_len + 3;
let sides = Box::new([
decode_rolling_ball_side(span, &mut position, int_width)?,
decode_rolling_ball_side(span, &mut position, int_width)?,
]);
let slice = decode_curve_block(span, position, int_width)?;
position = slice.end;
let offsets = [
take_f64(span, &mut position)? * LEN_TO_MM,
take_f64(span, &mut position)? * LEN_TO_MM,
];
let radius_selector = match span.get(position)? {
0x15 => {
if take_tagged_int(span, &mut position, 0x15, int_width)? != -1 {
return None;
}
EmbeddedRollingBallRadiusSelector::None
}
0x06 => EmbeddedRollingBallRadiusSelector::Value(take_f64(span, &mut position)?),
_ => return None,
};
let u_range = [
take_f64(span, &mut position)?,
take_f64(span, &mut position)?,
];
let v_range = [
take_f64(span, &mut position)?,
take_f64(span, &mut position)?,
];
let parameters = [
take_f64(span, &mut position)?,
take_f64(span, &mut position)?,
take_f64(span, &mut position)?,
];
let tail = take_tagged_int(span, &mut position, 0x04, int_width)?;
let cache = decode_surface_block(span, position, int_width)?;
position = cache.end;
let cache_fit_tolerance = Some(take_f64(span, &mut position)? * LEN_TO_MM);
let discontinuities = [
take_float_array(span, &mut position, int_width)?,
take_float_array(span, &mut position, int_width)?,
take_float_array(span, &mut position, int_width)?,
];
let third = if has_third {
Some(Box::new(decode_rolling_ball_third_side(
span,
&mut position,
int_width,
)?))
} else {
None
};
let radius = if offsets[0] == offsets[1] {
BlendRadiusLaw::Constant {
signed_radius: offsets[0],
}
} else {
BlendRadiusLaw::Linear {
start: offsets[0],
end: offsets[1],
}
};
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Blend {
supports: Box::new([None, None]),
spine: Some(slice.curve.clone()),
radius,
cross_section: BlendCrossSection::Circular,
native: Some(Box::new(EmbeddedRollingBall {
sides,
slice: slice.curve,
offsets,
radius_selector,
u_range,
v_range,
parameters,
tail,
discontinuities,
third,
})),
},
cache_fit_tolerance,
})
}
fn decode_rb_blend_spl_sur_fallback(
record_bytes: &[u8],
int_width: usize,
) -> Option<DecodedProceduralSurface> {
let names: [&[u8]; 4] = [
b"rb_blend_spl_sur",
b"rbblnsur",
b"pipe_spl_sur",
b"pipesur",
];
let (start, header_len) = names.into_iter().find_map(|name| {
record_bytes
.windows(name.len() + 3)
.position(|window| {
window[0] == 0x0f
&& matches!(window[1], 0x0d | 0x0e)
&& usize::from(window[2]) == name.len()
&& &window[3..] == name
})
.map(|start| (start, name.len() + 3))
})?;
let span = subtype_span(record_bytes, start, int_width)?;
let cache = marker_positions(span)
.into_iter()
.filter_map(|at| decode_surface_block(span, at, int_width))
.next_back()?;
let mut support_count = 0usize;
let mut radius_boundary = None;
let mut pos = header_len;
while pos < cache.end {
match span[pos] {
0x0d | 0x0e => {
let len = usize::from(*span.get(pos + 1)?);
let name = span.get(pos + 2..pos + 2 + len)?;
if [b"plane".as_slice(), b"sphere", b"cone", b"torus"].contains(&name) {
support_count += 1;
}
}
0x15 if read_int(span, pos + 1, int_width) == Some(-1) => radius_boundary = Some(pos),
_ => {}
}
pos = next_token(span, pos, int_width)?;
}
let boundary = radius_boundary?;
let mut radius_values = Vec::new();
let mut pos = header_len;
while pos < boundary {
if span[pos] == 0x06 {
radius_values.push(read_f64(span, pos + 1)?);
}
pos = next_token(span, pos, int_width)?;
}
let end = *radius_values.last()? * LEN_TO_MM;
let start = *radius_values.get(radius_values.len().checked_sub(2)?)? * LEN_TO_MM;
let radius = if start == end {
BlendRadiusLaw::Constant {
signed_radius: start,
}
} else {
BlendRadiusLaw::Linear { start, end }
};
let center_curve = marker_positions(span)
.into_iter()
.filter_map(|at| decode_curve_block(span, at, int_width))
.map(|decoded| decoded.curve)
.next_back();
let mut support_caches = marker_positions(span)
.into_iter()
.filter_map(|at| decode_surface_block(span, at, int_width))
.filter(|decoded| decoded.end < cache.end)
.map(|decoded| decoded.surface);
let supports = [
(support_count > 0).then(|| support_caches.next()).flatten(),
(support_count > 1).then(|| support_caches.next()).flatten(),
];
let cache_fit_tolerance = (span.get(cache.end) == Some(&0x06))
.then(|| read_f64(span, cache.end + 1).map(|v| v * LEN_TO_MM))
.flatten();
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Blend {
supports: Box::new(supports),
spine: center_curve,
radius,
cross_section: BlendCrossSection::Circular,
native: None,
},
cache_fit_tolerance,
})
}
pub fn decode_procedural_surface_resolving_refs(
record_bytes: &[u8],
active_bytes: &[u8],
tables: &SubtypeTables,
) -> Option<DecodedProceduralSurface> {
INT_WIDTHS.into_iter().find_map(|int_width| {
decode_procedural_resolving_refs(
record_bytes,
active_bytes,
tables,
&mut Vec::new(),
int_width,
)
})
}
fn decode_procedural_resolving_refs(
bytes: &[u8],
active_bytes: &[u8],
tables: &SubtypeTables,
seen: &mut Vec<usize>,
int_width: usize,
) -> Option<DecodedProceduralSurface> {
if let Some(decoded) = decode_exact_spl_sur(bytes, int_width)
.or_else(|| decode_comp_spl_sur(bytes, int_width))
.or_else(|| decode_taper_spl_sur(bytes, int_width))
.or_else(|| decode_loft_spl_sur(bytes, int_width))
.or_else(|| decode_compound_loft_spl_sur(bytes, int_width))
.or_else(|| decode_g2_blend_spl_sur(bytes, int_width))
.or_else(|| decode_ruled_spl_sur(bytes, int_width))
.or_else(|| decode_sum_spl_sur(bytes, int_width))
.or_else(|| decode_rot_spl_sur(bytes, int_width))
.or_else(|| decode_off_spl_sur(bytes, int_width))
.or_else(|| decode_cyl_spl_sur_at(bytes, int_width))
.or_else(|| decode_var_blend_spl_sur(bytes, int_width))
.or_else(|| decode_vertex_blend_spl_sur(bytes, int_width))
.or_else(|| decode_full_rb_blend_spl_sur(bytes, int_width))
.or_else(|| decode_rb_blend_spl_sur_fallback(bytes, int_width))
{
return Some(decoded);
}
let table = tables.for_width(int_width);
for index in subtype_refs(bytes, int_width) {
if seen.contains(&index) {
continue;
}
let target = *table.get(index)?;
seen.push(index);
if let Some(decoded) = decode_procedural_resolving_refs(
subtype_span(active_bytes, target, int_width)?,
active_bytes,
tables,
seen,
int_width,
) {
return Some(decoded);
}
}
None
}
pub fn decode_surface_cache_resolving_refs(
record_bytes: &[u8],
active_bytes: &[u8],
tables: &SubtypeTables,
) -> Option<NurbsSurface> {
INT_WIDTHS.into_iter().find_map(|int_width| {
decode_cache_resolving_refs(
record_bytes,
active_bytes,
tables,
&mut Vec::new(),
decode_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))
}
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_curve_cache_resolving_refs(
record_bytes: &[u8],
active_bytes: &[u8],
tables: &SubtypeTables,
) -> Option<NurbsCurve> {
INT_WIDTHS.into_iter().find_map(|int_width| {
decode_cache_resolving_refs(
record_bytes,
active_bytes,
tables,
&mut Vec::new(),
decode_curve_cache_at,
int_width,
)
})
}
pub(crate) type VectorOffsetDefinition = (NurbsCurve, [f64; 2], Vector3, [String; 2], [i64; 2]);
pub(crate) type SubsetDefinition = (NurbsCurve, [f64; 2]);
pub(crate) type CompoundDefinition = (Vec<f64>, Vec<f64>, Vec<NurbsCurve>);
pub(crate) struct EmbeddedTwoSidedOffset {
pub(crate) surfaces: [SurfaceGeometry; 2],
pub(crate) pcurves: [NurbsPcurve; 2],
pub(crate) parameter_range: [f64; 2],
pub(crate) discontinuities: [Vec<f64>; 3],
pub(crate) offsets: [f64; 2],
}
pub(crate) struct EmbeddedIntersection {
pub(crate) surfaces: [SurfaceGeometry; 2],
pub(crate) pcurves: [NurbsPcurve; 2],
pub(crate) parameter_range: [f64; 2],
pub(crate) discontinuities: [Vec<f64>; 3],
}
pub(crate) struct EmbeddedThreeSurfaceIntersection {
pub(crate) surfaces: [SurfaceGeometry; 3],
pub(crate) pcurves: [NurbsPcurve; 3],
pub(crate) parameter_range: [f64; 2],
pub(crate) discontinuities: [Vec<f64>; 3],
pub(crate) selector: i64,
}
pub(crate) struct EmbeddedProjection {
pub(crate) surfaces: [SurfaceGeometry; 2],
pub(crate) pcurves: [NurbsPcurve; 2],
pub(crate) parameter_range: [f64; 2],
pub(crate) discontinuities: [Vec<f64>; 3],
pub(crate) source: NurbsCurve,
pub(crate) tail: cadmpeg_ir::geometry::ProjectionTail,
}
pub(crate) struct EmbeddedSilhouette {
pub(crate) context: EmbeddedIntersection,
pub(crate) silhouette: cadmpeg_ir::geometry::SilhouetteKind,
pub(crate) cast_surface: SurfaceGeometry,
pub(crate) light_direction: Vector3,
}
pub(crate) struct EmbeddedSurfaceOffset {
pub(crate) context: EmbeddedIntersection,
pub(crate) base_u_range: [f64; 2],
pub(crate) base_v_range: [f64; 2],
pub(crate) base: NurbsCurve,
pub(crate) base_range: [f64; 2],
pub(crate) distance: f64,
pub(crate) shift: f64,
pub(crate) scale: f64,
}
pub(crate) struct EmbeddedSpring {
pub(crate) surfaces: [Option<SurfaceGeometry>; 2],
pub(crate) pcurves: [Option<NurbsPcurve>; 2],
pub(crate) surface_parameter_ranges: [Option<[[f64; 2]; 2]>; 2],
pub(crate) first_pcurve_parameter_range: Option<[f64; 2]>,
pub(crate) parameter_range: [f64; 2],
pub(crate) discontinuities: [Vec<f64>; 3],
pub(crate) direction: i64,
}
pub(crate) enum EmbeddedDeformableData {
VectorField {
vectors: [Vector3; 4],
parameter_pairs: Vec<[f64; 2]>,
},
Surface(SurfaceGeometry),
}
pub(crate) struct EmbeddedDeformable {
pub(crate) extension: i64,
pub(crate) bend: NurbsCurve,
pub(crate) data: EmbeddedDeformableData,
}
pub struct DecodedProceduralCurve {
pub curve: NurbsCurve,
pub native_kind: String,
pub definition: Option<cadmpeg_ir::geometry::ProceduralCurveDefinition>,
pub vector_offset: Option<VectorOffsetDefinition>,
pub subset: Option<SubsetDefinition>,
pub compound: Option<CompoundDefinition>,
pub(crate) embedded_two_sided_offset: Option<EmbeddedTwoSidedOffset>,
pub(crate) embedded_intersection: Option<EmbeddedIntersection>,
pub(crate) embedded_three_surface_intersection: Option<EmbeddedThreeSurfaceIntersection>,
pub(crate) embedded_surface_curve: Option<(
cadmpeg_ir::geometry::SurfaceCurveFamily,
EmbeddedIntersection,
)>,
pub(crate) embedded_silhouette: Option<EmbeddedSilhouette>,
pub(crate) embedded_surface_offset: Option<EmbeddedSurfaceOffset>,
pub(crate) embedded_spring: Option<EmbeddedSpring>,
pub(crate) embedded_deformable: Option<EmbeddedDeformable>,
pub(crate) embedded_projection: Option<EmbeddedProjection>,
pub cache_fit_tolerance: Option<f64>,
}
pub fn decode_procedural_curve_resolving_refs(
record_bytes: &[u8],
active_bytes: &[u8],
tables: &SubtypeTables,
) -> Option<DecodedProceduralCurve> {
INT_WIDTHS.into_iter().find_map(|int_width| {
decode_procedural_curve_recursive(
record_bytes,
active_bytes,
tables,
&mut Vec::new(),
int_width,
)
})
}
fn decode_procedural_curve_recursive(
bytes: &[u8],
active_bytes: &[u8],
tables: &SubtypeTables,
seen: &mut Vec<usize>,
int_width: usize,
) -> Option<DecodedProceduralCurve> {
let vector_offset = decode_vector_offset_definition(bytes, int_width);
let subset = decode_subset_definition(bytes, int_width);
let compound = decode_compound_definition(bytes, int_width);
let positions = marker_positions(bytes);
let solved = if vector_offset.is_some() || subset.is_some() || compound.is_some() {
positions
.into_iter()
.rev()
.find_map(|position| decode_curve_block(bytes, position, int_width))
} else {
positions
.into_iter()
.find_map(|position| decode_curve_block(bytes, position, int_width))
};
if let Some(decoded) = solved {
let cache_fit_tolerance = (bytes.get(decoded.end) == Some(&0x06))
.then(|| read_f64(bytes, decoded.end + 1).map(|value| value * LEN_TO_MM))
.flatten();
let native_kind =
first_construction_subtype(bytes).unwrap_or_else(|| "intcurve".to_string());
let definition = if native_kind == "exact_int_cur" {
Some(cadmpeg_ir::geometry::ProceduralCurveDefinition::Exact)
} else {
decode_helix_definition(bytes).or_else(|| decode_two_sided_offset(bytes, int_width))
};
return Some(DecodedProceduralCurve {
curve: decoded.curve,
native_kind,
definition,
vector_offset,
subset,
compound,
embedded_two_sided_offset: decode_embedded_two_sided_offset(bytes, int_width),
embedded_intersection: decode_embedded_intersection(bytes, int_width),
embedded_three_surface_intersection: decode_embedded_three_surface_intersection(
bytes, int_width,
),
embedded_surface_curve: decode_embedded_surface_curve(bytes, int_width),
embedded_silhouette: decode_embedded_silhouette(bytes, int_width),
embedded_surface_offset: decode_embedded_surface_offset(bytes, int_width),
embedded_spring: decode_embedded_spring(bytes, int_width),
embedded_deformable: decode_embedded_deformable(bytes, int_width),
embedded_projection: decode_embedded_projection(bytes, int_width),
cache_fit_tolerance,
});
}
let table = tables.for_width(int_width);
for index in subtype_refs(bytes, int_width) {
if seen.contains(&index) {
continue;
}
let target = *table.get(index)?;
seen.push(index);
if let Some(decoded) = decode_procedural_curve_recursive(
subtype_span(active_bytes, target, int_width)?,
active_bytes,
tables,
seen,
int_width,
) {
return Some(decoded);
}
}
None
}
fn decode_embedded_deformable(bytes: &[u8], int_width: usize) -> Option<EmbeddedDeformable> {
let name = b"defm_int_cur";
let marker = bytes.windows(name.len() + 3).position(|window| {
window[0] == 0x0f
&& matches!(window[1], 0x0d | 0x0e)
&& usize::from(window[2]) == name.len()
&& &window[3..] == name
})?;
let mut position = marker + name.len() + 3;
let extension = take_tagged_int(bytes, &mut position, 0x04, int_width)?;
let bend = decode_curve_block(bytes, position, int_width)?;
position = bend.end;
let mode = take_tagged_int(bytes, &mut position, 0x04, int_width)?;
let data = match mode {
8 => {
let mut vectors = [Vector3::new(0.0, 0.0, 0.0); 4];
for vector in &mut vectors {
let value = take_native_vec3(bytes, &mut position, 0x14)?;
*vector = Vector3::new(value[0], value[1], value[2]);
}
let count = take_tagged_int(bytes, &mut position, 0x04, int_width)?;
let count = usize::try_from(count).ok()?;
let mut parameter_pairs = Vec::with_capacity(count);
for _ in 0..count {
parameter_pairs.push([
take_f64(bytes, &mut position)?,
take_f64(bytes, &mut position)?,
]);
}
EmbeddedDeformableData::VectorField {
vectors,
parameter_pairs,
}
}
5 => EmbeddedDeformableData::Surface(decode_embedded_surface(
bytes,
&mut position,
int_width,
)?),
_ => return None,
};
Some(EmbeddedDeformable {
extension,
bend: bend.curve,
data,
})
}
fn decode_embedded_spring(bytes: &[u8], int_width: usize) -> Option<EmbeddedSpring> {
let name = b"spring_int_cur";
let (marker, name_len) = find_intcurve_subtype(bytes, name)?;
let mut position = marker + name_len + 3;
let mut surfaces = [None, None];
let mut surface_parameter_ranges = [None, None];
for side in 0..2 {
let saved = position;
if take_native_ident(bytes, &mut position).as_deref() == Some("null_surface") {
surface_parameter_ranges[side] = Some([
[
take_range_value(bytes, &mut position)?,
take_range_value(bytes, &mut position)?,
],
[
take_range_value(bytes, &mut position)?,
take_range_value(bytes, &mut position)?,
],
]);
} else {
position = saved;
surfaces[side] = Some(decode_embedded_surface(bytes, &mut position, int_width)?);
}
}
let first_pcurve;
let first_pcurve_parameter_range;
let saved = position;
if take_native_ident(bytes, &mut position).as_deref() == Some("nullbs") {
first_pcurve = None;
first_pcurve_parameter_range = Some([
take_range_value(bytes, &mut position)?,
take_range_value(bytes, &mut position)?,
]);
} else {
position = saved;
let (pcurve, end) = decode_pcurve_block_with_end(bytes, position, int_width)?;
position = end;
first_pcurve = Some(pcurve);
first_pcurve_parameter_range = None;
}
let saved = position;
let second_pcurve = if take_native_ident(bytes, &mut position).as_deref() == Some("nullbs") {
None
} else {
position = saved;
let (pcurve, end) = decode_pcurve_block_with_end(bytes, position, int_width)?;
position = end;
Some(pcurve)
};
let parameter_range = [
take_range_value(bytes, &mut position)?,
take_range_value(bytes, &mut position)?,
];
let discontinuities = [
take_float_array(bytes, &mut position, int_width)?,
take_float_array(bytes, &mut position, int_width)?,
take_float_array(bytes, &mut position, int_width)?,
];
take_bool(bytes, &mut position)?;
let direction = take_tagged_int(bytes, &mut position, 0x15, int_width)?;
Some(EmbeddedSpring {
surfaces,
pcurves: [first_pcurve, second_pcurve],
surface_parameter_ranges,
first_pcurve_parameter_range,
parameter_range,
discontinuities,
direction,
})
}
fn decode_embedded_surface_offset(bytes: &[u8], int_width: usize) -> Option<EmbeddedSurfaceOffset> {
let name = b"off_surf_int_cur";
let (marker, name_len) = find_intcurve_subtype(bytes, name)?;
let mut position = marker + name_len + 3;
let surfaces = [
decode_embedded_surface(bytes, &mut position, int_width)?,
decode_embedded_surface(bytes, &mut position, int_width)?,
];
let (first_pcurve, first_end) = decode_pcurve_block_with_end(bytes, position, int_width)?;
position = first_end;
let (second_pcurve, second_end) = decode_pcurve_block_with_end(bytes, position, int_width)?;
position = second_end;
let parameter_range = [
take_range_value(bytes, &mut position)?,
take_range_value(bytes, &mut position)?,
];
let discontinuities = [
take_float_array(bytes, &mut position, int_width)?,
take_float_array(bytes, &mut position, int_width)?,
take_float_array(bytes, &mut position, int_width)?,
];
take_bool(bytes, &mut position)?;
let base_u_range = [
take_range_value(bytes, &mut position)?,
take_range_value(bytes, &mut position)?,
];
let base_v_range = [
take_range_value(bytes, &mut position)?,
take_range_value(bytes, &mut position)?,
];
let base = decode_curve_block(bytes, position, int_width)?;
position = base.end;
let base_range = [
take_range_value(bytes, &mut position)?,
take_range_value(bytes, &mut position)?,
];
Some(EmbeddedSurfaceOffset {
context: EmbeddedIntersection {
surfaces,
pcurves: [first_pcurve, second_pcurve],
parameter_range,
discontinuities,
},
base_u_range,
base_v_range,
base: base.curve,
base_range,
distance: take_f64(bytes, &mut position)? * LEN_TO_MM,
shift: take_f64(bytes, &mut position)?,
scale: take_f64(bytes, &mut position)?,
})
}
fn decode_embedded_silhouette(bytes: &[u8], int_width: usize) -> Option<EmbeddedSilhouette> {
use cadmpeg_ir::geometry::SilhouetteKind;
let names = [
(b"silh_int_cur".as_slice(), SilhouetteKind::Standard),
(b"para_silh_int_cur".as_slice(), SilhouetteKind::Parametric),
(b"parasil".as_slice(), SilhouetteKind::Parametric),
(
b"taper_silh_int_cur".as_slice(),
SilhouetteKind::Taper { draft_factor: 0.0 },
),
];
let (marker, name, mut silhouette) = names.into_iter().find_map(|(name, silhouette)| {
bytes
.windows(name.len() + 3)
.position(|window| {
window[0] == 0x0f
&& matches!(window[1], 0x0d | 0x0e)
&& usize::from(window[2]) == name.len()
&& &window[3..] == name
})
.map(|marker| (marker, name, silhouette))
})?;
let mut position = marker + name.len() + 3;
let surfaces = [
decode_embedded_surface(bytes, &mut position, int_width)?,
decode_embedded_surface(bytes, &mut position, int_width)?,
];
let (first_pcurve, first_end) = decode_pcurve_block_with_end(bytes, position, int_width)?;
position = first_end;
let (second_pcurve, second_end) = decode_pcurve_block_with_end(bytes, position, int_width)?;
position = second_end;
let parameter_range = [
take_range_value(bytes, &mut position)?,
take_range_value(bytes, &mut position)?,
];
let discontinuities = [
take_float_array(bytes, &mut position, int_width)?,
take_float_array(bytes, &mut position, int_width)?,
take_float_array(bytes, &mut position, int_width)?,
];
let cast_surface = decode_embedded_surface(bytes, &mut position, int_width)?;
let light = take_native_vec3(bytes, &mut position, 0x14)?;
let light_direction = normalized(light)?;
if matches!(silhouette, SilhouetteKind::Taper { .. }) {
silhouette = SilhouetteKind::Taper {
draft_factor: take_f64(bytes, &mut position)?,
};
}
Some(EmbeddedSilhouette {
context: EmbeddedIntersection {
surfaces,
pcurves: [first_pcurve, second_pcurve],
parameter_range,
discontinuities,
},
silhouette,
cast_surface,
light_direction,
})
}
fn decode_embedded_surface_curve(
bytes: &[u8],
int_width: usize,
) -> Option<(
cadmpeg_ir::geometry::SurfaceCurveFamily,
EmbeddedIntersection,
)> {
use cadmpeg_ir::geometry::SurfaceCurveFamily;
let names = [
(b"blend_int_cur".as_slice(), SurfaceCurveFamily::Blend),
(b"bldcur".as_slice(), SurfaceCurveFamily::Blend),
(
b"surf_int_cur".as_slice(),
SurfaceCurveFamily::SurfaceConstrained,
),
(
b"surfcur".as_slice(),
SurfaceCurveFamily::SurfaceConstrained,
),
(b"par_int_cur".as_slice(), SurfaceCurveFamily::Parametric),
(b"parcur".as_slice(), SurfaceCurveFamily::Parametric),
(b"skin_int_cur".as_slice(), SurfaceCurveFamily::Skin),
(b"d5c2_cur".as_slice(), SurfaceCurveFamily::Skin),
];
let (marker, name, family) = names.into_iter().find_map(|(name, family)| {
bytes
.windows(name.len() + 3)
.position(|window| {
window[0] == 0x0f
&& matches!(window[1], 0x0d | 0x0e)
&& usize::from(window[2]) == name.len()
&& &window[3..] == name
})
.map(|marker| (marker, name, family))
})?;
let mut position = marker + name.len() + 3;
let surfaces = [
decode_embedded_surface(bytes, &mut position, int_width)?,
decode_embedded_surface(bytes, &mut position, int_width)?,
];
let (first_pcurve, first_end) = decode_pcurve_block_with_end(bytes, position, int_width)?;
position = first_end;
let (second_pcurve, second_end) = decode_pcurve_block_with_end(bytes, position, int_width)?;
position = second_end;
let parameter_range = [
take_range_value(bytes, &mut position)?,
take_range_value(bytes, &mut position)?,
];
let discontinuities = [
take_float_array(bytes, &mut position, int_width)?,
take_float_array(bytes, &mut position, int_width)?,
take_float_array(bytes, &mut position, int_width)?,
];
Some((
family,
EmbeddedIntersection {
surfaces,
pcurves: [first_pcurve, second_pcurve],
parameter_range,
discontinuities,
},
))
}
fn decode_embedded_three_surface_intersection(
bytes: &[u8],
int_width: usize,
) -> Option<EmbeddedThreeSurfaceIntersection> {
let name = b"sss_int_cur";
let marker = bytes.windows(name.len() + 3).position(|window| {
window[0] == 0x0f
&& matches!(window[1], 0x0d | 0x0e)
&& usize::from(window[2]) == name.len()
&& &window[3..] == name
})?;
let mut position = marker + name.len() + 3;
let first = decode_embedded_surface(bytes, &mut position, int_width)?;
let second = decode_embedded_surface(bytes, &mut position, int_width)?;
let (first_pcurve, first_end) = decode_pcurve_block_with_end(bytes, position, int_width)?;
position = first_end;
let (second_pcurve, second_end) = decode_pcurve_block_with_end(bytes, position, int_width)?;
position = second_end;
let parameter_range = [
take_range_value(bytes, &mut position)?,
take_range_value(bytes, &mut position)?,
];
let discontinuities = [
take_float_array(bytes, &mut position, int_width)?,
take_float_array(bytes, &mut position, int_width)?,
take_float_array(bytes, &mut position, int_width)?,
];
let selector = take_tagged_int(bytes, &mut position, 0x04, int_width)?;
let third = decode_embedded_surface(bytes, &mut position, int_width)?;
let (third_pcurve, _) = decode_pcurve_block_with_end(bytes, position, int_width)?;
Some(EmbeddedThreeSurfaceIntersection {
surfaces: [first, second, third],
pcurves: [first_pcurve, second_pcurve, third_pcurve],
parameter_range,
discontinuities,
selector,
})
}
fn decode_embedded_projection(bytes: &[u8], int_width: usize) -> Option<EmbeddedProjection> {
let name = b"proj_int_cur";
let (marker, name_len) = find_intcurve_subtype(bytes, name)?;
let mut position = marker + name_len + 3;
let surfaces = [
decode_embedded_surface(bytes, &mut position, int_width)?,
decode_embedded_surface(bytes, &mut position, int_width)?,
];
let (first_pcurve, first_end) = decode_pcurve_block_with_end(bytes, position, int_width)?;
position = first_end;
let (second_pcurve, second_end) = decode_pcurve_block_with_end(bytes, position, int_width)?;
position = second_end;
let parameter_range = [
take_range_value(bytes, &mut position)?,
take_range_value(bytes, &mut position)?,
];
let discontinuities = [
take_float_array(bytes, &mut position, int_width)?,
take_float_array(bytes, &mut position, int_width)?,
take_float_array(bytes, &mut position, int_width)?,
];
take_bool(bytes, &mut position)?;
let source = decode_curve_block(bytes, position, int_width)?;
position = source.end;
let flag = take_bool(bytes, &mut position)?;
let tail = if bytes.get(position) == Some(&0x10) {
cadmpeg_ir::geometry::ProjectionTail::EarlyClose { flag }
} else {
cadmpeg_ir::geometry::ProjectionTail::Ranged {
flag,
parameter_range: [
take_range_value(bytes, &mut position)?,
take_range_value(bytes, &mut position)?,
],
role: take_native_string(bytes, &mut position)?,
}
};
Some(EmbeddedProjection {
surfaces,
pcurves: [first_pcurve, second_pcurve],
parameter_range,
discontinuities,
source: source.curve,
tail,
})
}
fn decode_embedded_intersection(bytes: &[u8], int_width: usize) -> Option<EmbeddedIntersection> {
let names: [&[u8]; 3] = [b"int_int_cur", b"surf_surf_int_cur", b"surfintcur"];
let (marker, name) = names.into_iter().find_map(|name| {
bytes
.windows(name.len() + 3)
.position(|window| {
window[0] == 0x0f
&& matches!(window[1], 0x0d | 0x0e)
&& usize::from(window[2]) == name.len()
&& &window[3..] == name
})
.map(|marker| (marker, name))
})?;
let mut position = marker + name.len() + 3;
let surfaces = [
decode_embedded_surface(bytes, &mut position, int_width)?,
decode_embedded_surface(bytes, &mut position, int_width)?,
];
let (first_pcurve, first_end) = decode_pcurve_block_with_end(bytes, position, int_width)?;
position = first_end;
let (second_pcurve, second_end) = decode_pcurve_block_with_end(bytes, position, int_width)?;
position = second_end;
let parameter_range = [
take_range_value(bytes, &mut position)?,
take_range_value(bytes, &mut position)?,
];
let discontinuities = [
take_float_array(bytes, &mut position, int_width)?,
take_float_array(bytes, &mut position, int_width)?,
take_float_array(bytes, &mut position, int_width)?,
];
take_bool(bytes, &mut position)?;
Some(EmbeddedIntersection {
surfaces,
pcurves: [first_pcurve, second_pcurve],
parameter_range,
discontinuities,
})
}
fn decode_embedded_two_sided_offset(
bytes: &[u8],
int_width: usize,
) -> Option<EmbeddedTwoSidedOffset> {
let name = b"off_int_cur";
let (marker, name_len) = find_intcurve_subtype(bytes, name)?;
let mut position = marker + name_len + 3;
let first_surface = decode_embedded_surface(bytes, &mut position, int_width)?;
let second_surface = decode_embedded_surface(bytes, &mut position, int_width)?;
let surfaces = [first_surface, second_surface];
let (first_pcurve, first_end) = decode_pcurve_block_with_end(bytes, position, int_width)?;
position = first_end;
let (second_pcurve, second_end) = decode_pcurve_block_with_end(bytes, position, int_width)?;
position = second_end;
let pcurves = [first_pcurve, second_pcurve];
let parameter_range = [
take_range_value(bytes, &mut position)?,
take_range_value(bytes, &mut position)?,
];
let discontinuities = [
take_float_array(bytes, &mut position, int_width)?,
take_float_array(bytes, &mut position, int_width)?,
take_float_array(bytes, &mut position, int_width)?,
];
if !matches!(bytes.get(position), Some(0x0a | 0x0b)) {
return None;
}
position += 1;
let offsets = [
take_range_value(bytes, &mut position)? * LEN_TO_MM,
take_range_value(bytes, &mut position)? * LEN_TO_MM,
];
Some(EmbeddedTwoSidedOffset {
surfaces,
pcurves,
parameter_range,
discontinuities,
offsets,
})
}
fn decode_embedded_surface(
bytes: &[u8],
position: &mut usize,
int_width: usize,
) -> Option<SurfaceGeometry> {
let kind = take_native_ident(bytes, position)?;
if kind == "spline" {
let decoded = decode_surface_block(bytes, *position, int_width)?;
*position = decoded.end;
return Some(SurfaceGeometry::Nurbs(decoded.surface));
}
let point = take_native_vec3(bytes, position, 0x13)?;
let point = Point3::new(
point[0] * LEN_TO_MM,
point[1] * LEN_TO_MM,
point[2] * LEN_TO_MM,
);
match kind.as_str() {
"plane" => {
let normal = normalized(take_native_vec3(bytes, position, 0x14)?)?;
let u_axis = normalized(take_native_vec3(bytes, position, 0x14)?)?;
take_bool(bytes, position)?;
Some(SurfaceGeometry::Plane {
origin: point,
normal,
u_axis,
})
}
"cone" => {
let axis = normalized(take_native_vec3(bytes, position, 0x14)?)?;
let major = take_native_vec3(bytes, position, 0x14)?;
let ref_direction = normalized(major)?;
take_f64(bytes, position)?;
take_bool(bytes, position)?;
take_bool(bytes, position)?;
let sine = take_f64(bytes, position)?;
take_f64(bytes, position)?;
let radius = take_f64(bytes, position)? * LEN_TO_MM;
for _ in 0..5 {
take_bool(bytes, position)?;
}
if sine.abs() <= f64::EPSILON {
Some(SurfaceGeometry::Cylinder {
origin: point,
axis,
ref_direction,
radius,
})
} else {
Some(SurfaceGeometry::Cone {
origin: point,
axis,
ref_direction,
radius,
half_angle: sine.abs().asin(),
})
}
}
"sphere" => {
let radius = take_f64(bytes, position)? * LEN_TO_MM;
let ref_direction = normalized(take_native_vec3(bytes, position, 0x14)?)?;
let axis = normalized(take_native_vec3(bytes, position, 0x14)?)?;
for _ in 0..5 {
take_bool(bytes, position)?;
}
Some(SurfaceGeometry::Sphere {
center: point,
axis,
ref_direction,
radius,
})
}
"torus" => {
let axis = normalized(take_native_vec3(bytes, position, 0x14)?)?;
let major_radius = take_f64(bytes, position)? * LEN_TO_MM;
let minor_radius = take_f64(bytes, position)? * LEN_TO_MM;
let ref_direction = normalized(take_native_vec3(bytes, position, 0x14)?)?;
for _ in 0..5 {
take_bool(bytes, position)?;
}
Some(SurfaceGeometry::Torus {
center: point,
axis,
ref_direction,
major_radius,
minor_radius,
})
}
_ => None,
}
}
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)
}
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)
}
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))
}
fn decode_two_sided_offset(
bytes: &[u8],
int_width: usize,
) -> Option<cadmpeg_ir::geometry::ProceduralCurveDefinition> {
use cadmpeg_ir::geometry::{
IntcurveSupportContext, IntcurveSupportSide, ProceduralCurveDefinition,
};
let name = b"off_int_cur";
let (marker, name_len) = find_intcurve_subtype(bytes, name)?;
let mut position = marker + name_len + 3;
for expected in ["null_surface", "null_surface", "nullbs", "nullbs"] {
if take_native_ident(bytes, &mut position)?.as_str() != expected {
return None;
}
}
let parameter_range = [
take_range_value(bytes, &mut position)?,
take_range_value(bytes, &mut position)?,
];
let discontinuities = [
take_float_array(bytes, &mut position, int_width)?,
take_float_array(bytes, &mut position, int_width)?,
take_float_array(bytes, &mut position, int_width)?,
];
if !matches!(bytes.get(position), Some(0x0a | 0x0b)) {
return None;
}
position += 1;
let offsets = [
take_range_value(bytes, &mut position)? * LEN_TO_MM,
take_range_value(bytes, &mut position)? * LEN_TO_MM,
];
Some(ProceduralCurveDefinition::TwoSidedOffset {
context: IntcurveSupportContext {
sides: [
IntcurveSupportSide {
surface: None,
pcurve: None,
},
IntcurveSupportSide {
surface: None,
pcurve: None,
},
],
parameter_range,
discontinuities,
},
offsets,
})
}
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)
}
fn decode_compound_definition(bytes: &[u8], int_width: usize) -> Option<CompoundDefinition> {
let name = b"comp_int_cur";
let marker = bytes.windows(name.len() + 3).position(|window| {
window[0] == 0x0f
&& matches!(window[1], 0x0d | 0x0e)
&& usize::from(window[2]) == name.len()
&& &window[3..] == name
})?;
let mut position = marker + name.len() + 3;
let parameters = take_float_array(bytes, &mut position, int_width)?;
let count = usize::try_from(take_tagged_int(bytes, &mut position, 0x04, int_width)?).ok()?;
if count == 0 {
return None;
}
let mut component_parameters = Vec::with_capacity(count);
for _ in 0..count {
if bytes.get(position) != Some(&0x06) {
return None;
}
component_parameters.push(read_f64(bytes, position + 1)?);
position += 9;
}
if !matches!(bytes.get(position), Some(0x0a | 0x0b)) {
return None;
}
position += 1;
let mut components = Vec::with_capacity(count);
for _ in 0..count {
let relative = marker_positions(bytes.get(position..)?)
.into_iter()
.next()?;
let decoded = decode_curve_block(bytes, position + relative, int_width)?;
components.push(decoded.curve);
position = decoded.end;
}
Some((parameters, component_parameters, components))
}
fn take_float_array(bytes: &[u8], position: &mut usize, int_width: usize) -> Option<Vec<f64>> {
let count = usize::try_from(take_tagged_int(bytes, position, 0x04, int_width)?).ok()?;
let mut values = Vec::with_capacity(count);
for _ in 0..count {
if bytes.get(*position) != Some(&0x06) {
return None;
}
values.push(read_f64(bytes, *position + 1)?);
*position += 9;
}
Some(values)
}
fn decode_subset_definition(bytes: &[u8], int_width: usize) -> Option<SubsetDefinition> {
let name = b"subset_int_cur";
let (marker, name_len) = find_intcurve_subtype(bytes, name)?;
let start = marker + name_len + 3;
let source_marker = marker_positions(&bytes[start..]).into_iter().next()? + start;
let source = decode_curve_block(bytes, source_marker, int_width)?;
let mut position = source.end;
let range = [
take_range_value(bytes, &mut position)?,
take_range_value(bytes, &mut position)?,
];
Some((source.curve, range))
}
fn decode_vector_offset_definition(
bytes: &[u8],
int_width: usize,
) -> Option<VectorOffsetDefinition> {
let name = b"offset_int_cur";
let (marker, name_len) = find_intcurve_subtype(bytes, name)?;
let start = marker + name_len + 3;
let source_marker = marker_positions(&bytes[start..]).into_iter().next()? + start;
let source = decode_curve_block(bytes, source_marker, int_width)?;
let mut position = source.end;
if bytes.get(position) != Some(&0x06) || bytes.get(position + 9) != Some(&0x06) {
return None;
}
let parameter_range = [
read_f64(bytes, position + 1)?,
read_f64(bytes, position + 10)?,
];
position += 18;
let offset = take_native_vec3(bytes, &mut position, 0x14)?;
let first_label = take_native_string(bytes, &mut position)?;
let first_code = take_tagged_int(bytes, &mut position, 0x04, int_width)?;
let second_label = take_native_string(bytes, &mut position)?;
let second_code = take_tagged_int(bytes, &mut position, 0x04, int_width)?;
Some((
source.curve,
parameter_range,
Vector3::new(
offset[0] * LEN_TO_MM,
offset[1] * LEN_TO_MM,
offset[2] * LEN_TO_MM,
),
[first_label, second_label],
[first_code, second_code],
))
}
fn take_native_string(bytes: &[u8], position: &mut 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(u32_at(bytes, *position + 1)?).ok()?, 5),
_ => 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)
}
fn decode_helix_definition(
bytes: &[u8],
) -> Option<cadmpeg_ir::geometry::ProceduralCurveDefinition> {
let name = b"helix_int_cur";
let marker = bytes.windows(name.len() + 3).position(|window| {
window[0] == 0x0f
&& matches!(window[1], 0x0d | 0x0e)
&& usize::from(window[2]) == name.len()
&& &window[3..] == name
})?;
let mut position = marker + name.len() + 3;
let lower = take_range_value(bytes, &mut position)?;
let upper = take_range_value(bytes, &mut position)?;
let center = take_native_vec3(bytes, &mut position, 0x13)?;
let major = take_native_vec3(bytes, &mut position, 0x13)?;
let minor = take_native_vec3(bytes, &mut position, 0x13)?;
let pitch = take_native_vec3(bytes, &mut position, 0x13)?;
if bytes.get(position) != Some(&0x06) {
return None;
}
let apex_factor = read_f64(bytes, position + 1)?;
position += 9;
let axis = take_native_vec3(bytes, &mut position, 0x14)?;
Some(cadmpeg_ir::geometry::ProceduralCurveDefinition::Helix {
angle_range: [lower, upper],
center: Point3::new(
center[0] * LEN_TO_MM,
center[1] * LEN_TO_MM,
center[2] * LEN_TO_MM,
),
major: Vector3::new(
major[0] * LEN_TO_MM,
major[1] * LEN_TO_MM,
major[2] * LEN_TO_MM,
),
minor: Vector3::new(
minor[0] * LEN_TO_MM,
minor[1] * LEN_TO_MM,
minor[2] * LEN_TO_MM,
),
pitch: Vector3::new(
pitch[0] * LEN_TO_MM,
pitch[1] * LEN_TO_MM,
pitch[2] * LEN_TO_MM,
),
apex_factor,
axis: Vector3::new(axis[0], axis[1], axis[2]),
})
}
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)
}
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)
}
fn first_construction_subtype(bytes: &[u8]) -> Option<String> {
for pos in 0..bytes.len().saturating_sub(3) {
if bytes[pos] != 0x0f || !matches!(bytes.get(pos + 1), Some(0x0d | 0x0e)) {
continue;
}
let len = usize::from(*bytes.get(pos + 2)?);
let name = bytes.get(pos + 3..pos + 3 + len)?;
if name != b"ref" {
return Some(canonical_intcurve_kind(name).into());
}
}
None
}
fn canonical_intcurve_kind(name: &[u8]) -> &str {
match name {
b"bldcur" => "blend_int_cur",
b"blndsprngcur" => "spring_int_cur",
b"exactcur" => "exact_int_cur",
b"lawintcur" => "law_int_cur",
b"offintcur" => "off_int_cur",
b"offsetintcur" => "offset_int_cur",
b"offsurfintcur" => "off_surf_int_cur",
b"parasil" => "para_silh_int_cur",
b"parcur" => "par_int_cur",
b"projcur" => "proj_int_cur",
b"surfcur" => "surf_int_cur",
b"surfintcur" => "int_int_cur",
b"d5c2_cur" => "skin_int_cur",
b"subsetintcur" => "subset_int_cur",
_ => std::str::from_utf8(name).unwrap_or("intcurve"),
}
}
fn find_intcurve_subtype(bytes: &[u8], modern: &[u8]) -> Option<(usize, usize)> {
let legacy: &[u8] = match modern {
b"blend_int_cur" => b"bldcur",
b"spring_int_cur" => b"blndsprngcur",
b"exact_int_cur" => b"exactcur",
b"law_int_cur" => b"lawintcur",
b"off_int_cur" => b"offintcur",
b"offset_int_cur" => b"offsetintcur",
b"off_surf_int_cur" => b"offsurfintcur",
b"para_silh_int_cur" => b"parasil",
b"par_int_cur" => b"parcur",
b"proj_int_cur" => b"projcur",
b"surf_int_cur" => b"surfcur",
b"int_int_cur" => b"surfintcur",
b"skin_int_cur" => b"d5c2_cur",
b"subset_int_cur" => b"subsetintcur",
_ => b"",
};
[modern, legacy]
.into_iter()
.filter(|name| !name.is_empty())
.find_map(|name| {
bytes
.windows(name.len() + 3)
.position(|window| {
window[0] == 0x0f
&& matches!(window[1], 0x0d | 0x0e)
&& usize::from(window[2]) == name.len()
&& &window[3..] == name
})
.map(|marker| (marker, name.len()))
})
}
fn decode_cache_resolving_refs<T>(
bytes: &[u8],
active_bytes: &[u8],
tables: &SubtypeTables,
seen: &mut Vec<usize>,
decode_inline: fn(&[u8], usize) -> Option<T>,
int_width: usize,
) -> Option<T> {
if let Some(decoded) = decode_inline(bytes, int_width) {
return Some(decoded);
}
let table = tables.for_width(int_width);
for index in subtype_refs(bytes, int_width) {
if seen.contains(&index) {
continue;
}
let target = *table.get(index)?;
seen.push(index);
if let Some(decoded) = decode_cache_resolving_refs(
subtype_span(active_bytes, target, int_width)?,
active_bytes,
tables,
seen,
decode_inline,
int_width,
) {
return Some(decoded);
}
}
None
}
pub struct SubtypeTables {
tables: [Vec<usize>; INT_WIDTHS.len()],
}
impl SubtypeTables {
pub fn from_records(records: &[Record], bytes: &[u8]) -> Self {
Self {
tables: INT_WIDTHS.map(|walk_width| {
let mut table = Vec::new();
for record in records {
collect_defs_in_span(
bytes,
record.offset,
record.offset + record.len,
walk_width,
&mut table,
);
}
table
}),
}
}
pub fn from_stream(bytes: &[u8]) -> Self {
Self {
tables: INT_WIDTHS.map(|walk_width| {
let mut table = Vec::new();
collect_defs_in_span(bytes, 0, bytes.len(), walk_width, &mut table);
table
}),
}
}
fn for_width(&self, int_width: usize) -> &[usize] {
INT_WIDTHS
.iter()
.position(|&width| width == int_width)
.map_or(&[], |slot| self.tables[slot].as_slice())
}
}
fn collect_defs_in_span(
bytes: &[u8],
start: usize,
end: usize,
int_width: usize,
table: &mut Vec<usize>,
) {
let end = end.min(bytes.len());
let mut pos = start;
while pos < end {
if bytes[pos] == 0x0f && matches!(bytes.get(pos + 1), Some(0x0d | 0x0e)) {
let len = usize::from(*bytes.get(pos + 2).unwrap_or(&0));
if let Some(name) = bytes.get(pos + 3..pos + 3 + len) {
if name != b"ref" {
table.push(pos);
}
}
}
match next_token(bytes, pos, int_width) {
Some(next) => pos = next,
None => return,
}
}
}
fn subtype_refs(bytes: &[u8], int_width: usize) -> Vec<usize> {
let mut refs = Vec::new();
let marker = b"\x0f\x0d\x03ref\x04";
let mut pos = 0usize;
while pos < bytes.len() {
if bytes[pos..].starts_with(marker) {
if let Some(index) = read_int(bytes, pos + marker.len(), int_width) {
if index >= 0 {
refs.push(index as usize);
}
}
}
match next_token(bytes, pos, int_width) {
Some(next) => pos = next,
None => break,
}
}
refs
}
fn subtype_span(bytes: &[u8], start: usize, int_width: usize) -> Option<&[u8]> {
let mut depth = 0usize;
let mut pos = start;
while pos < bytes.len() {
match bytes[pos] {
0x0f => depth += 1,
0x10 => {
depth = depth.checked_sub(1)?;
if depth == 0 {
return bytes.get(start..=pos);
}
}
_ => {}
}
pos = next_token(bytes, pos, int_width)?;
}
None
}
fn next_token(bytes: &[u8], pos: usize, int_width: usize) -> Option<usize> {
let tag = *bytes.get(pos)?;
let fixed = match tag {
0x02 => 2,
0x03 => 3,
0x04 | 0x0c | 0x15 => 1 + int_width,
0x06 | 0x17 => 9,
0x05 => 5,
0x0a | 0x0b | 0x0f | 0x10 | 0x11 => 1,
0x13 | 0x14 => 25,
0x16 => 17,
0x07 | 0x0d | 0x0e => 2 + usize::from(*bytes.get(pos + 1)?),
0x08 => {
3 + usize::from(u16::from_le_bytes(
bytes.get(pos + 1..pos + 3)?.try_into().ok()?,
))
}
0x09 | 0x12 => {
5 + usize::try_from(u32::from_le_bytes(
bytes.get(pos + 1..pos + 5)?.try_into().ok()?,
))
.ok()?
}
_ => return None,
};
let next = pos.checked_add(fixed)?;
(next <= bytes.len()).then_some(next)
}
pub fn decode_pcurve_cache(record_bytes: &[u8]) -> Option<NurbsPcurve> {
INT_WIDTHS
.into_iter()
.find_map(|int_width| decode_pcurve_cache_at(record_bytes, int_width))
}
fn decode_pcurve_cache_at(record_bytes: &[u8], int_width: usize) -> Option<NurbsPcurve> {
marker_positions(record_bytes)
.into_iter()
.find_map(|pos| decode_pcurve_block(record_bytes, pos, int_width))
}
pub fn decode_pcurve_cache_resolving_refs(
record_bytes: &[u8],
active_bytes: &[u8],
tables: &SubtypeTables,
) -> Option<NurbsPcurve> {
INT_WIDTHS.into_iter().find_map(|int_width| {
decode_cache_resolving_refs(
record_bytes,
active_bytes,
tables,
&mut Vec::new(),
decode_pcurve_cache_at,
int_width,
)
})
}
pub fn decode_pcurve_cache_candidates_resolving_refs(
record_bytes: &[u8],
active_bytes: &[u8],
tables: &SubtypeTables,
) -> Vec<NurbsPcurve> {
for int_width in INT_WIDTHS {
let mut out = Vec::new();
collect_pcurve_candidates(
record_bytes,
active_bytes,
tables,
&mut Vec::new(),
int_width,
&mut out,
);
if !out.is_empty() {
return out;
}
}
Vec::new()
}
fn collect_pcurve_candidates(
bytes: &[u8],
active_bytes: &[u8],
tables: &SubtypeTables,
seen: &mut Vec<usize>,
int_width: usize,
out: &mut Vec<NurbsPcurve>,
) {
let mut ambiguous = Vec::new();
for position in marker_positions(bytes) {
if let Some(pcurve) = decode_pcurve_block(bytes, position, int_width) {
if decode_curve_block(bytes, position, int_width).is_some() {
ambiguous.push(pcurve);
} else {
out.push(pcurve);
}
}
}
out.append(&mut ambiguous);
let table = tables.for_width(int_width);
for index in subtype_refs(bytes, int_width) {
if seen.contains(&index) {
continue;
}
seen.push(index);
let Some(&target) = table.get(index) else {
continue;
};
let Some(span) = subtype_span(active_bytes, target, int_width) else {
continue;
};
collect_pcurve_candidates(span, active_bytes, tables, seen, int_width, out);
}
}
#[cfg(test)]
mod width_tests {
use super::*;
fn push_int(out: &mut Vec<u8>, tag: u8, value: i64, int_width: usize) {
out.push(tag);
if int_width == 4 {
out.extend_from_slice(
&i32::try_from(value)
.expect("test value fits i32")
.to_le_bytes(),
);
} else {
out.extend_from_slice(&value.to_le_bytes());
}
}
fn push_f64(out: &mut Vec<u8>, value: f64) {
out.push(0x06);
out.extend_from_slice(&value.to_le_bytes());
}
fn curve_block(int_width: usize) -> Vec<u8> {
let mut b = NUBS_MARKER.to_vec();
push_int(&mut b, 0x04, 1, int_width); push_int(&mut b, 0x15, 0, int_width); push_int(&mut b, 0x04, 2, int_width); push_f64(&mut b, 0.0);
push_int(&mut b, 0x04, 1, int_width);
push_f64(&mut b, 1.0);
push_int(&mut b, 0x04, 1, int_width);
for component in [0.0, 0.0, 0.0, 1.0, 2.0, 3.0] {
push_f64(&mut b, component);
}
b
}
fn surface_block(int_width: usize) -> Vec<u8> {
let mut b = NUBS_MARKER.to_vec();
push_int(&mut b, 0x04, 1, int_width); push_int(&mut b, 0x04, 1, int_width); for _ in 0..4 {
push_int(&mut b, 0x15, 0, int_width); }
push_int(&mut b, 0x04, 2, int_width); push_int(&mut b, 0x04, 2, int_width); for _ in 0..2 {
push_f64(&mut b, 0.0);
push_int(&mut b, 0x04, 1, int_width);
push_f64(&mut b, 1.0);
push_int(&mut b, 0x04, 1, int_width);
}
for pole in 0..4 {
push_f64(&mut b, f64::from(pole));
push_f64(&mut b, 0.0);
push_f64(&mut b, 0.0);
}
b
}
#[test]
fn curve_cache_decodes_in_both_integer_widths() {
for int_width in [4usize, 8] {
let curve = decode_curve_cache(&curve_block(int_width))
.unwrap_or_else(|| panic!("curve cache at width {int_width}"));
assert_eq!(curve.degree, 1);
assert_eq!(curve.control_points.len(), 2);
assert_eq!(curve.control_points[1].x, 10.0); assert_eq!(curve.knots, vec![0.0, 0.0, 1.0, 1.0]);
}
}
#[test]
fn surface_cache_decodes_in_both_integer_widths() {
for int_width in [4usize, 8] {
let surface = decode_surface_cache(&surface_block(int_width))
.unwrap_or_else(|| panic!("surface cache at width {int_width}"));
assert_eq!((surface.u_degree, surface.v_degree), (1, 1));
assert_eq!((surface.u_count, surface.v_count), (2, 2));
}
}
#[test]
fn surface_cache_resolves_width4_subtype_ref() {
let mut active = vec![0x0f, 0x0d, 0x07];
active.extend_from_slice(b"spl_sur");
active.extend_from_slice(&surface_block(4));
active.push(0x10);
let mut record = vec![0x0f, 0x0d, 0x03];
record.extend_from_slice(b"ref");
push_int(&mut record, 0x04, 0, 4);
record.push(0x10);
let surface = decode_surface_cache_resolving_refs(
&record,
&active,
&SubtypeTables::from_stream(&active),
)
.expect("resolved width-4 ref");
assert_eq!((surface.u_count, surface.v_count), (2, 2));
}
}