use cadmpeg_ir::geometry::SurfaceGeometry;
use cadmpeg_ir::le::u16_at as u16_le;
use cadmpeg_ir::math::{Point3, Vector3};
#[cfg(test)]
use std::collections::BTreeMap;
use crate::families::a5a8::records::A8Surface;
#[cfg(test)]
use crate::wire::bytes::persistent_ref;
use crate::wire::bytes::{
allocation_ref, compact_int, f64_le, finite_f64_lane, read_f64_array, u32_le_24,
};
#[cfg(test)]
use crate::wire::records::consolidated_records;
use crate::wire::records::{
b_family_frames, parse_consolidated_pcurve, ConsolidatedFrame, ConsolidatedPcurve,
};
#[derive(Debug, Clone)]
pub struct B2OffsetSupport {
pub pos: usize,
pub support_id: u32,
pub distance: f64,
pub domain: [f64; 4],
}
#[derive(Debug, Clone, PartialEq)]
#[cfg(test)]
pub enum B2ParameterPoint {
Uv {
pos: usize,
uv: [f64; 2],
},
StationUv {
pos: usize,
station: f64,
uv: [f64; 2],
},
FiveScalars {
pos: usize,
values: [f64; 5],
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg(test)]
pub struct B2ReferenceList {
pub pos: usize,
pub references: Vec<u32>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg(test)]
pub struct B2OwnerPacket {
pub pos: usize,
pub header_token: u32,
pub reference_encoding: B2OwnerReferenceEncoding,
pub references: [u32; 9],
pub numeric_tail: [u8; 62],
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg(test)]
pub struct B2CountedOwner {
pub pos: usize,
pub header_token: u32,
pub references: Vec<u32>,
pub tail: Vec<u8>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg(test)]
pub enum B2OwnerReferenceEncoding {
TaggedU16Strong,
WidthCodedStrong,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg(test)]
pub struct B2Counted61 {
pub pos: usize,
pub header_token: u32,
pub references: Vec<u32>,
pub tail: Vec<u8>,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg(test)]
pub struct B2Long61 {
pub pos: usize,
pub header_token: u32,
pub prefix: [u8; 8],
pub members: Vec<u16>,
pub references: [u16; 5],
pub scalar: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg(test)]
pub struct B2Link5f {
pub pos: usize,
pub header_token: u32,
pub target: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg(test)]
pub struct B2LinkedOwner {
pub link: B2Link5f,
pub owner: B2OwnerPacket,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg(test)]
pub struct B2LinkedCountedOwner {
pub link: B2Link5f,
pub owner: B2CountedOwner,
}
#[derive(Debug, Clone, PartialEq)]
#[cfg(test)]
pub struct B2ConeFace {
pub pos: usize,
pub references: Vec<u32>,
pub angular_scale: f64,
pub half_angle: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum B2UseSense {
Sense84,
Sense88,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct B2UseMetadata {
pub pos: usize,
pub payload: Vec<u8>,
pub references: Option<Vec<u32>>,
pub sense: Option<B2UseSense>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg(test)]
pub struct B2EdgeMetadata {
pub pos: usize,
pub payload: Vec<u8>,
pub references: Vec<u16>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct B2EdgeNode {
pub pos: usize,
pub header_token: u32,
pub curve_ref: u32,
pub start_vertex_ref: u32,
pub end_vertex_ref: u32,
pub start_parameter_ref: u32,
pub end_parameter_ref: u32,
pub tail: u8,
}
#[must_use]
pub fn b2_use_metadata(data: &[u8]) -> Vec<B2UseMetadata> {
b_family_frames(data, 0x06)
.into_iter()
.map(|frame| {
let payload = data[frame.payload..frame.end].to_vec();
let sense = match payload.last() {
Some(0x84) => Some(B2UseSense::Sense84),
Some(0x88) => Some(B2UseSense::Sense88),
_ => None,
};
let references = sense.and_then(|_| {
let end = frame.end.checked_sub(1)?;
let count = usize::from(data.get(frame.payload)?.checked_sub(0x80)?);
let mut at = frame.payload + 1;
let mut references = Vec::new();
for _ in 0..count {
references.push(compact_int(data, &mut at)?);
}
(at == end).then_some(references)
});
B2UseMetadata {
pos: frame.pos,
payload,
references,
sense,
}
})
.collect()
}
#[must_use]
#[cfg(test)]
pub fn b2_edge_metadata(data: &[u8]) -> Vec<B2EdgeMetadata> {
b_family_frames(data, 0x5e)
.into_iter()
.map(|frame| {
let payload = data[frame.payload..frame.end].to_vec();
let mut references = Vec::new();
let mut at = 0;
while at < payload.len() {
if payload[at] == 0x0a && at + 3 <= payload.len() {
references.push(u16::from_le_bytes([payload[at + 1], payload[at + 2]]));
at += 3;
} else {
at += 1;
}
}
B2EdgeMetadata {
pos: frame.pos,
payload,
references,
}
})
.collect()
}
#[must_use]
pub fn b2_edge_nodes(data: &[u8]) -> Vec<B2EdgeNode> {
b_family_frames(data, 0x5e)
.into_iter()
.filter_map(|frame| {
let mut at = frame.payload;
let curve_ref = compact_int(data, &mut at)?;
let start_vertex_ref = allocation_ref(data, &mut at)?;
let end_vertex_ref = allocation_ref(data, &mut at)?;
let start_parameter_ref = compact_int(data, &mut at)?;
let end_parameter_ref = compact_int(data, &mut at)?;
let tail = *data.get(at)?;
(at + 1 == frame.end).then_some(B2EdgeNode {
pos: frame.pos,
header_token: frame.header_token,
curve_ref,
start_vertex_ref,
end_vertex_ref,
start_parameter_ref,
end_parameter_ref,
tail,
})
})
.collect()
}
#[must_use]
#[cfg(test)]
pub fn b2_cone_faces(data: &[u8]) -> Vec<B2ConeFace> {
b_family_frames(data, 0x3b)
.into_iter()
.filter_map(|frame| {
if frame.header_token != 5 || frame.end - frame.payload != 0x20 {
return None;
}
let scalar_at = frame.end - 16;
let angular_scale = f64_le(data, scalar_at)?;
let half_angle = f64_le(data, scalar_at + 8)?;
if !angular_scale.is_finite()
|| !(0.0..std::f64::consts::FRAC_PI_2).contains(&half_angle)
{
return None;
}
let mut at = frame.payload;
let mut references = Vec::new();
while at < scalar_at {
references.push(compact_int(data, &mut at)?);
}
(at == scalar_at).then_some(B2ConeFace {
pos: frame.pos,
references,
angular_scale,
half_angle,
})
})
.collect()
}
#[must_use]
#[cfg(test)]
pub fn b2_reference_lists(data: &[u8]) -> Vec<B2ReferenceList> {
b_family_frames(data, 0x37)
.into_iter()
.filter_map(|frame| {
if frame.header_token != 5
|| !matches!(frame.end - frame.payload, 0x22 | 0x24 | 0x26)
|| f64_le(data, frame.end.checked_sub(8)?)? != 1.0
{
return None;
}
let refs_end = frame.end - 8;
let mut at = frame.payload;
let mut references = Vec::new();
while at < refs_end {
references.push(compact_int(data, &mut at)?);
}
(at == refs_end).then_some(B2ReferenceList {
pos: frame.pos,
references,
})
})
.collect()
}
#[must_use]
#[cfg(test)]
pub fn b2_counted_owners(data: &[u8]) -> Vec<B2CountedOwner> {
b_family_frames(data, 0x62)
.into_iter()
.filter_map(|frame| {
let count = usize::from(data.get(frame.payload)?.checked_sub(0x80)?);
if count == 0 {
return None;
}
let mut at = frame.payload + 1;
let references = (0..count)
.map(|_| persistent_ref(data, &mut at))
.collect::<Option<Vec<_>>>()?;
(at < frame.end).then(|| B2CountedOwner {
pos: frame.pos,
header_token: frame.header_token,
references,
tail: data[at..frame.end].to_vec(),
})
})
.collect()
}
#[must_use]
#[cfg(test)]
pub fn b2_owner_packets(data: &[u8]) -> Vec<B2OwnerPacket> {
b_family_frames(data, 0x62)
.into_iter()
.filter_map(|frame| {
if data.get(frame.payload) != Some(&0x89) {
return None;
}
let mut at = frame.payload + 1;
let reference_encoding = if data.get(at) == Some(&0x0a) {
B2OwnerReferenceEncoding::TaggedU16Strong
} else {
B2OwnerReferenceEncoding::WidthCodedStrong
};
let mut references = [0u32; 9];
for (index, reference) in references.iter_mut().enumerate() {
*reference = match (reference_encoding, index % 2) {
(B2OwnerReferenceEncoding::TaggedU16Strong, 0) => {
persistent_ref(data, &mut at)?
}
(B2OwnerReferenceEncoding::TaggedU16Strong, 1)
| (B2OwnerReferenceEncoding::WidthCodedStrong, 0) => {
compact_int(data, &mut at)?
}
(B2OwnerReferenceEncoding::WidthCodedStrong, 1) => {
let value = u32::from(*data.get(at)?);
at += 1;
value
}
_ => unreachable!(),
};
}
let numeric_tail = data.get(at..frame.end)?.try_into().ok()?;
Some(B2OwnerPacket {
pos: frame.pos,
header_token: frame.header_token,
reference_encoding,
references,
numeric_tail,
})
})
.collect()
}
#[must_use]
#[cfg(test)]
pub fn b2_counted_61(data: &[u8]) -> Vec<B2Counted61> {
b_family_frames(data, 0x61)
.into_iter()
.filter_map(|frame| {
let count = usize::from(data.get(frame.payload)?.checked_sub(0x80)?);
if count == 0 {
return None;
}
let mut at = frame.payload + 1;
let references = (0..count)
.map(|_| compact_int(data, &mut at))
.collect::<Option<Vec<_>>>()?;
let tail = data.get(at..frame.end)?;
if tail.is_empty() || tail.last() != Some(&0x03) {
return None;
}
Some(B2Counted61 {
pos: frame.pos,
header_token: frame.header_token,
references,
tail: tail.to_vec(),
})
})
.collect()
}
#[must_use]
#[cfg(test)]
pub fn b2_long_61(data: &[u8]) -> Vec<B2Long61> {
b_family_frames(data, 0x61)
.into_iter()
.filter_map(|frame| {
let payload_len = frame.end.checked_sub(frame.payload)?;
let delimiter = frame.end.checked_sub(25)?;
if payload_len < 36
|| data.get(frame.payload + 8) != Some(&0x06)
|| data.get(delimiter) != Some(&0xfe)
|| (delimiter - (frame.payload + 9)) % 2 != 0
|| data.get(frame.end - 1) != Some(&0x03)
{
return None;
}
let prefix = data
.get(frame.payload..frame.payload + 8)?
.try_into()
.ok()?;
let members = data[frame.payload + 9..delimiter]
.chunks_exact(2)
.map(|bytes| u16::from_le_bytes([bytes[0], bytes[1]]))
.collect::<Vec<_>>();
if members.is_empty() || members.windows(2).any(|pair| pair[0] >= pair[1]) {
return None;
}
let mut at = delimiter + 1;
let mut references = [0u16; 5];
for reference in &mut references {
if data.get(at) != Some(&0x0a) {
return None;
}
*reference = u16_le(data, at + 1)?;
at += 3;
}
let scalar = f64_le(data, at)?;
if !scalar.is_finite() || at + 9 != frame.end {
return None;
}
Some(B2Long61 {
pos: frame.pos,
header_token: frame.header_token,
prefix,
members,
references,
scalar,
})
})
.collect()
}
#[must_use]
#[cfg(test)]
pub fn b2_links_5f(data: &[u8]) -> Vec<B2Link5f> {
b_family_frames(data, 0x5f)
.into_iter()
.filter_map(|frame| {
if data.get(frame.payload) != Some(&0x82) {
return None;
}
let mut at = frame.payload + 1;
let target = compact_int(data, &mut at)?;
(at + 2 == frame.end && data.get(at..frame.end) == Some(&[0x03, 0x05])).then_some(
B2Link5f {
pos: frame.pos,
header_token: frame.header_token,
target,
},
)
})
.collect()
}
#[must_use]
#[cfg(test)]
pub fn b2_linked_owners(data: &[u8]) -> Vec<B2LinkedOwner> {
let links = b2_links_5f(data)
.into_iter()
.map(|value| (value.pos, value))
.collect::<BTreeMap<_, _>>();
let owners = b2_owner_packets(data)
.into_iter()
.map(|value| (value.pos, value))
.collect::<BTreeMap<_, _>>();
consolidated_records(data)
.windows(2)
.filter_map(|window| {
let [link_record, owner_record] = window else {
return None;
};
let link = links.get(&link_record.range.start)?;
let owner = owners.get(&owner_record.range.start)?;
(link.target.checked_add(1) == Some(owner.references[8])).then(|| B2LinkedOwner {
link: *link,
owner: owner.clone(),
})
})
.collect()
}
#[must_use]
#[cfg(test)]
pub fn b2_linked_counted_owners(data: &[u8]) -> Vec<B2LinkedCountedOwner> {
let links = b2_links_5f(data)
.into_iter()
.map(|value| (value.pos, value))
.collect::<BTreeMap<_, _>>();
let owners = b2_counted_owners(data)
.into_iter()
.map(|value| (value.pos, value))
.collect::<BTreeMap<_, _>>();
consolidated_records(data)
.windows(2)
.filter_map(|window| {
let [link_record, owner_record] = window else {
return None;
};
let link = links.get(&link_record.range.start)?;
let owner = owners.get(&owner_record.range.start)?;
(link.target.checked_add(1) == owner.references.last().copied()).then(|| {
B2LinkedCountedOwner {
link: *link,
owner: owner.clone(),
}
})
})
.collect()
}
#[must_use]
#[cfg(test)]
pub fn b2_parameter_points(data: &[u8]) -> Vec<B2ParameterPoint> {
b_family_frames(data, 0x18)
.into_iter()
.filter_map(|frame| {
if frame.header_token != 5 || data.get(frame.payload) != Some(&0x05) {
return None;
}
let at = frame.payload + 2;
match frame.end - frame.payload {
0x12 => Some(B2ParameterPoint::Uv {
pos: frame.pos,
uv: read_f64_array::<2>(data, at)?,
}),
0x1a => {
let values = read_f64_array::<3>(data, at)?;
Some(B2ParameterPoint::StationUv {
pos: frame.pos,
station: values[0],
uv: [values[1], values[2]],
})
}
0x2a => Some(B2ParameterPoint::FiveScalars {
pos: frame.pos,
values: read_f64_array::<5>(data, at)?,
}),
_ => None,
}
.filter(|value| match value {
B2ParameterPoint::Uv { uv, .. } => uv.iter().all(|v| v.is_finite()),
B2ParameterPoint::StationUv { station, uv, .. } => {
station.is_finite() && uv.iter().all(|v| v.is_finite())
}
B2ParameterPoint::FiveScalars { values, .. } => {
values.iter().all(|v| v.is_finite())
}
})
})
.collect()
}
#[must_use]
pub fn b2_class25_descriptors(data: &[u8]) -> Vec<B2Class25Descriptor> {
b_family_frames(data, 0x18)
.into_iter()
.filter_map(|frame| {
if frame.header_token != 5 {
return None;
}
let mut at = frame.payload;
let record_id = compact_int(data, &mut at)?;
let control = *data.get(at)?;
at += 1;
if !matches!(control, 0x02 | 0x0a) {
return None;
}
let values = finite_f64_lane(data.get(at..frame.end)?)?;
matches!(values.len(), 2 | 3).then_some(B2Class25Descriptor {
pos: frame.pos,
record_id,
control,
values,
})
})
.collect()
}
#[derive(Debug, Clone)]
pub struct B2EdgeParameters {
pub pos: usize,
pub range: [f64; 2],
pub tolerance: f64,
}
#[derive(Debug, Clone, PartialEq)]
pub struct B2Class25Descriptor {
pub pos: usize,
pub record_id: u32,
pub control: u8,
pub values: Vec<f64>,
}
pub(crate) fn b2_cone_point(cone: &B2Cone, uv: [f64; 2]) -> Option<Point3> {
if !(cone.slant_range[0] - 1e-6..=cone.slant_range[1] + 1e-6).contains(&uv[1]) {
return None;
}
let phi = uv[0] / cone.angular_scale;
let radial = [
phi.cos() * cone.t1[0] + phi.sin() * cone.t2[0],
phi.cos() * cone.t1[1] + phi.sin() * cone.t2[1],
phi.cos() * cone.t1[2] + phi.sin() * cone.t2[2],
];
let axial = cone.half_angle.cos();
let transverse = cone.half_angle.sin();
Some(Point3::new(
cone.apex[0] + uv[1] * (axial * cone.axis[0] + transverse * radial[0]),
cone.apex[1] + uv[1] * (axial * cone.axis[1] + transverse * radial[1]),
cone.apex[2] + uv[1] * (axial * cone.axis[2] + transverse * radial[2]),
))
}
pub(crate) fn b2_cylinder_point(cylinder: &B2Cylinder, uv: [f64; 2]) -> Option<Point3> {
let SurfaceGeometry::Cylinder {
origin,
axis,
ref_direction,
radius,
} = cylinder.geometry.as_ref()?
else {
return None;
};
if !(cylinder.u_range[0] - 1e-6..=cylinder.u_range[1] + 1e-6).contains(&uv[0])
|| !(cylinder.v_range[0] - 1e-6..=cylinder.v_range[1] + 1e-6).contains(&uv[1])
{
return None;
}
let angle = uv[0] / radius;
let perpendicular = (*axis).cross(*ref_direction);
Some(Point3::new(
origin.x
+ uv[1] * axis.x
+ radius * (angle.cos() * ref_direction.x + angle.sin() * perpendicular.x),
origin.y
+ uv[1] * axis.y
+ radius * (angle.cos() * ref_direction.y + angle.sin() * perpendicular.y),
origin.z
+ uv[1] * axis.z
+ radius * (angle.cos() * ref_direction.z + angle.sin() * perpendicular.z),
))
}
pub(crate) fn point_distance(a: Point3, b: Point3) -> f64 {
((a.x - b.x).powi(2) + (a.y - b.y).powi(2) + (a.z - b.z).powi(2)).sqrt()
}
#[derive(Debug, Clone)]
pub struct B2Circle {
pub pos: usize,
pub record_id: u32,
pub frame_token: u8,
pub center_pair: [f64; 2],
pub radius: f64,
pub range: [f64; 2],
pub full_circle: bool,
}
#[derive(Debug, Clone)]
pub struct B2Cylinder {
pub pos: usize,
#[cfg(test)]
pub layout: u8,
pub geometry: Option<SurfaceGeometry>,
pub u_range: [f64; 2],
pub v_range: [f64; 2],
#[cfg(test)]
pub stored_vector: Option<[f64; 2]>,
#[cfg(test)]
pub phase: Option<f64>,
}
#[derive(Debug, Clone)]
pub struct B2Cone {
pub pos: usize,
pub apex: [f64; 3],
pub t1: [f64; 3],
pub t2: [f64; 3],
pub axis: [f64; 3],
pub half_angle: f64,
pub slant_range: [f64; 2],
pub angular_scale: f64,
}
#[derive(Debug, Clone)]
#[cfg(test)]
pub struct B2Revolution {
pub profile_curve_id: u16,
pub origin: [f64; 3],
pub axis: [f64; 3],
pub profile_range: [f64; 2],
}
#[derive(Debug, Clone)]
#[cfg(test)]
pub struct B2GroupSeparator {
pub token: u32,
}
#[derive(Debug, Clone)]
pub struct B2Group {
pub pos: usize,
#[cfg(test)]
pub group_id: u32,
pub group_type: u32,
}
#[derive(Debug, Clone)]
pub struct B2ConstructionUse {
pub pos: usize,
pub support_id: u32,
pub distance: f64,
pub kind: u8,
pub domain: Option<[f64; 4]>,
}
#[derive(Debug, Clone)]
pub struct B2EmbeddedCylinder {
pub wrapper_pos: usize,
pub pos: usize,
pub object_id: u32,
pub cylinder: B2Cylinder,
}
#[must_use]
pub fn b2_embedded_cylinders(data: &[u8]) -> Vec<B2EmbeddedCylinder> {
let groups = b2_groups(data);
let mut out = Vec::new();
for (index, group) in groups.iter().enumerate() {
if group.group_type != 3 {
continue;
}
let wrapper_pos = group.pos;
let end = groups
.get(index + 1)
.map_or(data.len(), |next| next.pos)
.min(wrapper_pos.saturating_add(2500));
let mut search = wrapper_pos + 3;
while search + 3 <= end {
let Some(relative) = data[search..end]
.windows(3)
.position(|bytes| bytes == [0x03, 0x28, 0x5a])
else {
break;
};
let marker = search + relative;
search = marker + 3;
let mut payload = marker + 3;
let Some(object_id) = compact_int(data, &mut payload) else {
continue;
};
let Some(payload_end) = payload.checked_add(90) else {
continue;
};
if payload_end > end {
continue;
}
let mut standalone = vec![0xb2, 0x03, 0x28, 0x5a, 0];
standalone.extend_from_slice(&data[payload..payload_end]);
let Some(mut cylinder) = parse_b2_cylinder(
&standalone,
ConsolidatedFrame {
pos: 0,
payload: 5,
end: 95,
header_token: 0,
},
) else {
continue;
};
cylinder.pos = marker - 1;
out.push(B2EmbeddedCylinder {
wrapper_pos,
pos: marker - 1,
object_id,
cylinder,
});
}
}
out
}
#[must_use]
pub fn b2_construction_uses(data: &[u8]) -> Vec<B2ConstructionUse> {
let mut out = Vec::new();
for frame in b_family_frames(data, 0x30) {
let pos = frame.pos;
let payload = frame.payload;
if frame.header_token != 5 || data.get(payload) != Some(&0x05) {
continue;
}
let (support_id, at) = match data.get(payload + 1) {
Some(0x08) => {
let Some(value) = u16_le(data, payload + 2) else {
continue;
};
(u32::from(value), payload + 4)
}
Some(0x0c) => {
let Some(value) = u32_le_24(data, payload + 2) else {
continue;
};
(value, payload + 5)
}
_ => continue,
};
let Some(distance) = f64_le(data, at) else {
continue;
};
let Some(&kind) = data.get(at + 8) else {
continue;
};
let Some(fields) = read_f64_array::<4>(data, at + 9) else {
continue;
};
if at + 41 != frame.end || !distance.is_finite() || fields.iter().any(|v| !v.is_finite()) {
continue;
}
out.push(B2ConstructionUse {
pos,
support_id,
distance,
kind,
domain: (kind == 0x01).then_some([fields[0], fields[2], fields[1], fields[3]]),
});
}
out
}
#[must_use]
pub fn b2_cones(data: &[u8]) -> Vec<B2Cone> {
let mut out = Vec::new();
for frame in b_family_frames(data, 0x29) {
let pos = frame.pos;
let p = frame.payload;
if frame.end - p != 0xb8 || p + 153 > frame.end {
continue;
}
let Some(apex) = read_f64_array::<3>(data, p) else {
continue;
};
let Some(t1) = read_f64_array::<3>(data, p + 24) else {
continue;
};
let Some(t2) = read_f64_array::<3>(data, p + 48) else {
continue;
};
let Some(axis) = read_f64_array::<3>(data, p + 72) else {
continue;
};
let Some(half_angle) = f64_le(data, p + 96) else {
continue;
};
let Some(angular_offset) = f64_le(data, p + 120) else {
continue;
};
let Some(slant_range) = read_f64_array::<2>(data, p + 128) else {
continue;
};
let Some(angular_scale) = f64_le(data, p + 144) else {
continue;
};
let unit = |v: [f64; 3]| ((v[0] * v[0] + v[1] * v[1] + v[2] * v[2]) - 1.0).abs() < 1e-9;
if unit(t1)
&& unit(t2)
&& unit(axis)
&& (0.0..std::f64::consts::FRAC_PI_2).contains(&half_angle)
&& (0.0..1e6).contains(&angular_scale)
&& 0.0 < slant_range[0]
&& slant_range[0] < slant_range[1]
&& slant_range[1] < 1e6
&& apex
.iter()
.chain(&[angular_offset])
.all(|value| value.is_finite())
{
out.push(B2Cone {
pos,
apex,
t1,
t2,
axis,
half_angle,
slant_range,
angular_scale,
});
}
}
out
}
#[must_use]
#[cfg(test)]
pub fn b2_revolutions(data: &[u8]) -> Vec<B2Revolution> {
let mut out = Vec::new();
for frame in b_family_frames(data, 0x2d) {
let p = frame.payload;
if frame.end - p != 0xae
|| !matches!(data.get(p), Some(0x08 | 0x0a))
|| data.get(p + 131..p + 133) != Some(&[0x05, 0x05])
|| f64_le(data, p + 141) != Some(1.0)
|| f64_le(data, p + 149) != Some(1.0)
|| f64_le(data, p + 157) != Some(0.0)
|| data.get(p + 165) != Some(&0x01)
{
continue;
}
let Some(profile_curve_id) = u16_le(data, p + 1) else {
continue;
};
let Some(axis_frame) = read_f64_array::<12>(data, p + 3) else {
continue;
};
let Some(bounds) = read_f64_array::<4>(data, p + 99) else {
continue;
};
let Some(angular_scale) = f64_le(data, p + 133) else {
continue;
};
let Some(mean_angle_parameter) = f64_le(data, p + 166) else {
continue;
};
if axis_frame
.iter()
.chain(&bounds)
.chain(&[angular_scale, mean_angle_parameter])
.any(|value| !value.is_finite())
|| angular_scale <= 0.0
|| bounds[0] / angular_scale != 0.5
|| (bounds[1] - bounds[0]) / angular_scale != std::f64::consts::TAU
|| mean_angle_parameter / angular_scale != std::f64::consts::PI + 0.5
{
continue;
}
out.push(B2Revolution {
profile_curve_id,
origin: axis_frame[0..3].try_into().expect("three origin values"),
axis: axis_frame[9..12].try_into().expect("three axis values"),
profile_range: [bounds[2], bounds[3]],
});
}
out
}
#[must_use]
#[cfg(test)]
pub fn b2_group_separators(data: &[u8]) -> Vec<B2GroupSeparator> {
b_family_frames(data, 0x65)
.into_iter()
.filter(|frame| data.get(frame.payload..frame.end) == Some(&[0x81, 0x03, 0x05, 0x0d]))
.map(|frame| B2GroupSeparator {
token: frame.header_token,
})
.collect()
}
#[must_use]
pub fn b2_groups(data: &[u8]) -> Vec<B2Group> {
b_family_frames(data, 0x60)
.into_iter()
.filter_map(|frame| {
let mut at = frame.payload;
let group_id = compact_int(data, &mut at)?;
#[cfg(not(test))]
let _ = group_id;
let group_type = compact_int(data, &mut at)?;
(at == frame.end).then_some(B2Group {
pos: frame.pos,
#[cfg(test)]
group_id,
group_type,
})
})
.collect()
}
#[must_use]
pub fn b2_cone_geometry(cone: &B2Cone) -> SurfaceGeometry {
let slant = cone.slant_range[0];
let axial = slant * cone.half_angle.cos();
SurfaceGeometry::Cone {
origin: Point3::new(
cone.apex[0] + axial * cone.axis[0],
cone.apex[1] + axial * cone.axis[1],
cone.apex[2] + axial * cone.axis[2],
),
axis: Vector3::new(cone.axis[0], cone.axis[1], cone.axis[2]),
ref_direction: Vector3::new(cone.t1[0], cone.t1[1], cone.t1[2]),
radius: slant * cone.half_angle.sin(),
ratio: 1.0,
half_angle: cone.half_angle,
}
}
#[must_use]
pub fn b2_cylinders(data: &[u8]) -> Vec<B2Cylinder> {
b_family_frames(data, 0x28)
.into_iter()
.filter_map(|frame| parse_b2_cylinder(data, frame))
.collect()
}
fn parse_b2_cylinder(data: &[u8], frame: ConsolidatedFrame) -> Option<B2Cylinder> {
let pos = frame.pos;
let layout = u8::try_from(frame.end.checked_sub(frame.payload)?).ok()?;
let p = frame.payload;
let origin_values = read_f64_array::<3>(data, p)?;
let origin = Point3::new(origin_values[0], origin_values[1], origin_values[2]);
let frame_token = *data.get(p + 24)?;
match layout {
0x5a => {
if data.get(p + 89) != Some(&0x07) {
return None;
}
let vector = read_f64_array::<2>(data, p + 25)?;
let one = f64_le(data, p + 41)?;
let radius = f64_le(data, p + 49)?;
let u_range = read_f64_array::<2>(data, p + 57)?;
let v_range = read_f64_array::<2>(data, p + 73)?;
if one != 1.0
|| !(0.0..1e6).contains(&radius)
|| (vector[0].hypot(vector[1]) - 1.0).abs() > 1e-9
|| ((u_range[1] - u_range[0]) - 2.0 * std::f64::consts::PI * radius).abs() > 1e-6
{
return None;
}
let axis = match frame_token {
0x19 => Vector3::new(vector[0], vector[1], 0.0),
0x1c => Vector3::new(vector[1], -vector[0], 0.0),
_ => return None,
};
let ref_direction = Vector3::new(-axis.y, axis.x, 0.0);
Some(B2Cylinder {
pos,
#[cfg(test)]
layout,
geometry: Some(SurfaceGeometry::Cylinder {
origin,
axis,
ref_direction,
radius,
}),
u_range,
v_range,
#[cfg(test)]
stored_vector: None,
#[cfg(test)]
phase: None,
})
}
0x52 => {
if frame_token != 0x1d
|| f64_le(data, p + 25)? != 1.0
|| f64_le(data, p + 33)? != 1.0
|| data.get(p + 81) != Some(&0x07)
{
return None;
}
let radius = f64_le(data, p + 41)?;
let u_range = read_f64_array::<2>(data, p + 49)?;
let v_range = read_f64_array::<2>(data, p + 65)?;
if !(0.0..1e6).contains(&radius)
|| ((u_range[1] - u_range[0]) - 2.0 * std::f64::consts::PI * radius).abs() > 1e-6
{
return None;
}
Some(B2Cylinder {
pos,
#[cfg(test)]
layout,
geometry: Some(SurfaceGeometry::Cylinder {
origin,
axis: Vector3::new(1.0, 0.0, 0.0),
ref_direction: Vector3::new(0.0, 1.0, 0.0),
radius,
}),
u_range,
v_range,
#[cfg(test)]
stored_vector: None,
#[cfg(test)]
phase: None,
})
}
0x62 if frame_token == 0x0e && data.get(p + 89) == Some(&0x03) => {
let vector = read_f64_array::<2>(data, p + 25)?;
let one = f64_le(data, p + 41)?;
let radius = f64_le(data, p + 49)?;
let u_range = read_f64_array::<2>(data, p + 57)?;
let v_range = read_f64_array::<2>(data, p + 73)?;
let phase = f64_le(data, p + 90)?;
if one != 1.0
|| !(0.0..1e6).contains(&radius)
|| origin_values.iter().any(|value| !value.is_finite())
|| vector.iter().any(|value| !value.is_finite())
|| u_range.iter().any(|value| !value.is_finite())
|| v_range.iter().any(|value| !value.is_finite())
|| (vector[0].hypot(vector[1]) - 1.0).abs() > 1e-9
|| !phase.is_finite()
|| u_range[0] >= u_range[1]
|| v_range[0] >= v_range[1]
|| u_range[1] - u_range[0] > 2.0 * std::f64::consts::PI * radius + 1e-6
{
return None;
}
Some(B2Cylinder {
pos,
#[cfg(test)]
layout,
geometry: None,
u_range,
v_range,
#[cfg(test)]
stored_vector: Some(vector),
#[cfg(test)]
phase: Some(phase),
})
}
_ => None,
}
}
#[must_use]
pub fn b2_circles(data: &[u8]) -> Vec<B2Circle> {
let mut out = Vec::new();
for frame in b_family_frames(data, 0x19) {
let pos = frame.pos;
if !(0x32..=0x34).contains(&(frame.end - frame.payload)) {
continue;
}
let Ok(frame_token) = u8::try_from(frame.header_token) else {
continue;
};
let mut at = frame.payload;
let Some(record_id) = compact_int(data, &mut at) else {
continue;
};
let Some(values) = read_f64_array::<5>(data, at) else {
continue;
};
let [c1, c2, radius, lo, hi] = values;
if values.iter().all(|v| v.is_finite())
&& (0.0..1e6).contains(&radius)
&& c1.abs() <= 1e6
&& c2.abs() <= 1e6
&& hi > lo
{
out.push(B2Circle {
pos,
record_id,
frame_token,
center_pair: [c1, c2],
radius,
range: [lo, hi],
full_circle: ((hi - lo) - 2.0 * std::f64::consts::PI * radius).abs() < 1e-9,
});
}
}
out
}
#[must_use]
pub fn b2_edge_parameters(data: &[u8]) -> Vec<B2EdgeParameters> {
let mut out = Vec::new();
for frame in b_family_frames(data, 0x23) {
let pos = frame.pos;
if frame.end - frame.payload != 0x4e {
continue;
}
let Some(values) = read_f64_array::<9>(data, frame.payload + 6) else {
continue;
};
if values.iter().all(|v| v.is_finite())
&& values[0] == values[3]
&& values[0] == values[6]
&& values[1] == values[4]
&& values[1] == values[7]
&& values[5] == 1.0
&& values[2] == values[8]
{
out.push(B2EdgeParameters {
pos,
range: [values[0], values[1]],
tolerance: values[2],
});
}
}
out
}
#[must_use]
pub fn b2_offset_supports(data: &[u8]) -> Vec<B2OffsetSupport> {
let mut offsets = b_family_frames(data, 0x31)
.into_iter()
.filter_map(|frame| {
if frame.header_token != 5 {
return None;
}
let length = frame.end - frame.payload;
let (support_id, at) = match data.get(frame.payload) {
Some(0x08) if length == 0x2b => (
u32::from(u16_le(data, frame.payload + 1)?),
frame.payload + 3,
),
Some(0x0c) if length == 0x2c => {
(u32_le_24(data, frame.payload + 1)?, frame.payload + 4)
}
_ => return None,
};
let values = read_f64_array::<5>(data, at)?;
values
.iter()
.all(|v| v.is_finite())
.then_some(B2OffsetSupport {
pos: frame.pos,
support_id,
distance: values[0],
domain: [values[1], values[2], values[3], values[4]],
})
})
.collect::<Vec<_>>();
offsets.extend(
b2_construction_uses(data)
.into_iter()
.filter_map(|construction| {
if construction.kind != 0x01 {
return None;
}
Some(B2OffsetSupport {
pos: construction.pos,
support_id: construction.support_id,
distance: construction.distance,
domain: construction.domain?,
})
}),
);
offsets.sort_unstable_by_key(|offset| offset.pos);
offsets
}
#[must_use]
pub fn offset_support_carriers(
offsets: &[B2OffsetSupport],
carriers: &[A8Surface],
) -> Vec<Option<usize>> {
const PARAMETER_TOLERANCE: f64 = 1e-3;
offsets
.iter()
.map(|offset| {
let [u0, v0, u1, v1] = offset.domain;
let candidates = carriers
.iter()
.enumerate()
.filter_map(|(index, carrier)| {
let SurfaceGeometry::Nurbs(surface) = &carrier.geometry else {
return None;
};
let u_min = *surface.u_knots.first()?;
let u_max = *surface.u_knots.last()?;
let v_min = *surface.v_knots.first()?;
let v_max = *surface.v_knots.last()?;
let contains = u0 >= u_min - PARAMETER_TOLERANCE
&& u1 <= u_max + PARAMETER_TOLERANCE
&& v0 >= v_min - PARAMETER_TOLERANCE
&& v1 <= v_max + PARAMETER_TOLERANCE;
let has_v_limit = |limit: f64| {
surface
.v_knots
.iter()
.any(|knot| (*knot - limit).abs() <= PARAMETER_TOLERANCE)
};
(contains && has_v_limit(v0) && has_v_limit(v1)).then_some(index)
})
.collect::<Vec<_>>();
<[usize; 1]>::try_from(candidates).ok().map(|[index]| index)
})
.collect()
}
#[must_use]
pub fn b2_pcurves(data: &[u8]) -> Vec<ConsolidatedPcurve> {
b_family_frames(data, 0x20)
.into_iter()
.filter_map(|frame| parse_consolidated_pcurve(data, frame.pos, frame.payload, frame.end))
.collect()
}