use crate::kernel_header::RefWidth;
use crate::nurbs::blend::{
decode_optional_rolling_ball_surface, decode_rolling_ball_side, optional_rolling_ball_surface,
surface_ranges,
};
use crate::nurbs::core::{
curve_block, decode_curve_block, decode_surface_block, owned_curve_cache_resolving_refs,
owned_surface_cache_resolving_refs, surface_block,
};
use crate::nurbs::pcurve::{decode_pcurve_block_with_end, pcurve_block_with_end};
use crate::nurbs::proc_surface::{
decode_nullable_embedded_pcurve, ellipse_to_nurbs, law_formula, nullable_embedded_pcurve,
EmbeddedLawFormula,
};
use crate::nurbs::reader::{
normalized, take_bool, take_double_payload, take_float_array_payloads, take_native_ident,
take_native_string, take_native_vec3, take_range_value, take_tagged_int, Nullable, LEN_TO_MM,
};
use crate::nurbs::subtypes::{
find_owned_intcurve_subtype, find_owned_subtype_marker, subtype_span,
};
use crate::nurbs::support_chart::{cone_axis_reversed, cone_is_circular_cylinder, SupportChart};
use crate::nurbs::toks::{Cur, SubtypeTable};
use crate::sab::Token;
use cadmpeg_ir::features::FinitePoint3;
use cadmpeg_ir::geometry::analytic::{
ConeSurface, CylinderSurface, PlaneSurface, SphereSurface, TorusSurface,
};
use cadmpeg_ir::geometry::{
nurbs::NurbsCurve, pcurve::PcurveNurbs, SolvedSurfaceGeometry, SurfaceGeometry,
};
use cadmpeg_ir::math::{Point3, Vector3};
use cadmpeg_ir::scalar::{
Angle, NonNegativeLength, NonZeroLength, PositiveI64, PositiveLength, PositiveReal,
};
use cadmpeg_ir::units::{OrthonormalFrame3, UnitVector3};
const EPS_PARAMETER_AGREEMENT: f64 = 1.0e-12;
macro_rules! propagate_resource {
($result:expr) => {
match $result {
Ok(value) => value,
Err(error) => return Some(Err(error)),
}
};
}
pub type VectorOffsetDefinition = (
NurbsCurve,
[f64; 2],
Vector3,
cadmpeg_ir::geometry::VectorOffsetRoles,
);
pub type SubsetDefinition = (NurbsCurve, [f64; 2]);
pub struct CompoundDefinition {
pub parameters: Vec<f64>,
pub components: Vec<cadmpeg_ir::geometry::CompoundComponent<NurbsCurve>>,
}
pub struct EmbeddedTwoSidedOffset {
pub surfaces: [Option<SurfaceGeometry>; 2],
pub pcurves: [Option<PcurveNurbs>; 2],
pub parameter_range: [f64; 2],
pub discontinuities: [Vec<f64>; 3],
pub discontinuity_flag: bool,
pub offsets: [f64; 2],
}
pub enum SupportSlot {
Absent,
DeclaredOnly,
Surface(SurfaceGeometry),
}
impl SupportSlot {
pub(crate) fn into_surface(self) -> Option<SurfaceGeometry> {
match self {
Self::Surface(surface) => Some(surface),
Self::Absent | Self::DeclaredOnly => None,
}
}
}
pub struct EmbeddedIntersection {
pub surfaces: [SupportSlot; 2],
pub pcurves: [Option<PcurveNurbs>; 2],
pub parameter_range: [f64; 2],
pub discontinuities: [Vec<f64>; 3],
}
pub enum EmbeddedSurfaceCurveLayout<F> {
ContextFirst(EmbeddedIntersection),
CacheFirst {
context: CacheFirstCurveContext,
flags: F,
},
}
impl<F> EmbeddedSurfaceCurveLayout<F> {
fn selected_pcurve(&self, slot: usize) -> Option<&PcurveNurbs> {
let (surfaces, pcurves) = match self {
Self::ContextFirst(context) => (&context.surfaces, &context.pcurves),
Self::CacheFirst { context, .. } => (&context.surfaces, &context.pcurves),
};
selected_support_pcurve(surfaces, pcurves, slot)
}
fn try_map_flags<G>(
self,
map: impl FnOnce(F) -> Option<G>,
) -> Option<EmbeddedSurfaceCurveLayout<G>> {
Some(match self {
Self::ContextFirst(context) => EmbeddedSurfaceCurveLayout::ContextFirst(context),
Self::CacheFirst { context, flags } => EmbeddedSurfaceCurveLayout::CacheFirst {
context,
flags: map(flags)?,
},
})
}
}
pub enum EmbeddedSurfaceCurve {
Blend(EmbeddedSurfaceCurveLayout<bool>),
SurfaceConstrained(EmbeddedSurfaceCurveLayout<bool>),
Parametric(EmbeddedSurfaceCurveLayout<cadmpeg_ir::geometry::ParametricSurfaceCurveFlags>),
Skin(EmbeddedSurfaceCurveLayout<bool>),
}
impl EmbeddedSurfaceCurve {
fn selected_pcurve(&self, slot: usize) -> Option<&PcurveNurbs> {
match self {
Self::Blend(layout) | Self::SurfaceConstrained(layout) | Self::Skin(layout) => {
layout.selected_pcurve(slot)
}
Self::Parametric(layout) => layout.selected_pcurve(slot),
}
}
}
struct RequiredSupportPair {
surfaces: [SurfaceGeometry; 2],
pcurves: [PcurveNurbs; 2],
}
fn required_support_pair(cur: &mut Cur<'_>) -> Option<RequiredSupportPair> {
let first = embedded_support(cur)?;
let second = embedded_support(cur)?;
let (mut first_pcurve, first_end) = pcurve_block_with_end(cur.toks(), cur.pos())?;
cur.set_pos(first_end);
let (mut second_pcurve, second_end) = pcurve_block_with_end(cur.toks(), cur.pos())?;
cur.set_pos(second_end);
first.chart.normalize(&mut first_pcurve)?;
second.chart.normalize(&mut second_pcurve)?;
Some(RequiredSupportPair {
surfaces: [first.surface, second.surface],
pcurves: [first_pcurve, second_pcurve],
})
}
pub struct EmbeddedThreeSurfaceIntersection {
pub surfaces: [SurfaceGeometry; 3],
pub pcurves: [PcurveNurbs; 3],
pub parameter_range: [f64; 2],
pub discontinuities: [Vec<f64>; 3],
pub selector: i64,
}
pub struct EmbeddedProjection {
pub surfaces: [SurfaceGeometry; 2],
pub pcurves: [PcurveNurbs; 2],
pub parameter_range: [f64; 2],
pub discontinuities: [Vec<f64>; 3],
pub discontinuity_flag: bool,
pub source: NurbsCurve,
pub tail: cadmpeg_ir::geometry::ProjectionTail,
}
pub struct EmbeddedSilhouette {
pub surfaces: [SurfaceGeometry; 2],
pub pcurves: [PcurveNurbs; 2],
pub parameter_range: [f64; 2],
pub discontinuities: [Vec<f64>; 3],
pub silhouette: cadmpeg_ir::geometry::SilhouetteKind,
pub cast_surface: SurfaceGeometry,
pub light_direction: cadmpeg_ir::units::UnitVector3,
}
pub struct EmbeddedSurfaceOffset {
pub layout: EmbeddedSurfaceOffsetLayout,
pub base_u_range: [f64; 2],
pub base_v_range: [f64; 2],
pub base: NurbsCurve,
pub base_range: [f64; 2],
pub distance: f64,
pub shift: f64,
pub scale: f64,
}
pub enum EmbeddedSurfaceOffsetLayout {
ContextFirst {
context: Box<EmbeddedIntersection>,
discontinuity_flag: bool,
},
CacheFirst {
context: Box<CacheFirstCurveContext>,
base_endpoints: [Option<f64>; 2],
},
}
pub enum EmbeddedSpringSupport {
Surface(SurfaceGeometry),
Ranges([[f64; 2]; 2]),
}
pub enum EmbeddedSpringPcurve {
Pcurve(PcurveNurbs),
Range([f64; 2]),
}
pub enum EmbeddedSpringLayout {
ContextFirst {
supports: Box<[EmbeddedSpringSupport; 2]>,
first_pcurve: Box<EmbeddedSpringPcurve>,
second_pcurve: Option<PcurveNurbs>,
parameter_range: [f64; 2],
discontinuities: [Vec<f64>; 3],
discontinuity_flag: bool,
},
CacheFirst {
context: Box<CacheFirstCurveContext>,
},
}
pub struct EmbeddedSpring {
pub layout: EmbeddedSpringLayout,
pub direction: i64,
}
pub struct EmbeddedLawCurve {
pub surfaces: [SupportSlot; 2],
pub pcurves: [Option<PcurveNurbs>; 2],
pub discontinuities: [Vec<f64>; 3],
pub layout: EmbeddedLawCurveLayout,
pub extension: i64,
pub primary: EmbeddedLawFormula,
pub additional: Vec<EmbeddedLawFormula>,
}
pub enum EmbeddedLawCurveLayout {
Legacy([f64; 2]),
Version {
stamp: i64,
post_enum: i64,
parameter_range: [Option<f64>; 2],
},
}
pub enum EmbeddedDeformableData {
VectorField {
vectors: [Vector3; 4],
parameter_pairs: Vec<[f64; 2]>,
},
Mode3 {
leading_vectors: [Vector3; 4],
leading_parameter: f64,
leading_flags: [bool; 3],
trailing_point: Point3,
trailing_vectors: [Vector3; 2],
frame_parameter: f64,
frame_flags: [bool; 2],
parameters: [f64; 3],
trailing_flags: [bool; 5],
trailing_parameter: f64,
trailing_value: i64,
},
}
pub struct EmbeddedDeformable {
pub context: Box<CacheFirstCurveContext>,
pub source: EmbeddedDeformableSource,
pub source_parameter_range: [Option<f64>; 2],
pub data: EmbeddedDeformableData,
}
pub enum EmbeddedDeformableSource {
Curve(NurbsCurve),
NativeReference {
flag: bool,
index: i64,
},
}
pub struct HelixDefinition {
pub angle_range: [f64; 2],
pub center: Point3,
pub major: Vector3,
pub minor: Vector3,
pub pitch: Vector3,
pub apex_factor: f64,
pub axis: Vector3,
}
impl HelixDefinition {
pub(crate) fn into_definition(
self,
) -> Result<cadmpeg_ir::geometry::ProceduralCurveDefinition, &'static str> {
Ok(cadmpeg_ir::geometry::ProceduralCurveDefinition::Helix(
cadmpeg_ir::geometry::HelixCurveConstruction::try_new(
self.angle_range,
cadmpeg_ir::geometry::HelixFrame {
center: self.center,
major: self.major,
minor: self.minor,
pitch: self.pitch,
axis: self.axis,
},
self.apex_factor,
None,
)?,
))
}
}
pub enum ProceduralCurveConstruction {
Exact,
Helix(HelixDefinition),
VectorOffset(VectorOffsetDefinition),
Subset(SubsetDefinition),
Compound(CompoundDefinition),
TwoSidedOffset(EmbeddedTwoSidedOffset),
Intersection(EmbeddedIntersection, bool),
ThreeSurface(EmbeddedThreeSurfaceIntersection),
SurfaceCurve(EmbeddedSurfaceCurve),
Silhouette(EmbeddedSilhouette),
SurfaceOffset(EmbeddedSurfaceOffset),
Spring(EmbeddedSpring),
Deformable(EmbeddedDeformable),
Projection(EmbeddedProjection),
Law(EmbeddedLawCurve),
Unknown(String),
}
pub struct DecodedProceduralCurve {
pub curve: NurbsCurve,
pub construction: ProceduralCurveConstruction,
pub cache_fit_tolerance: Option<f64>,
}
pub fn procedural_curve_resolving_refs(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
toks: &[Token],
table: &SubtypeTable,
) -> Option<Result<DecodedProceduralCurve, cadmpeg_core::CodecError>> {
procedural_curve_recursive(ctx, toks, table, &mut std::collections::HashSet::new())
}
pub fn pcurve_for_selector_with_chart(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
toks: &[Token],
selector: i64,
table: &SubtypeTable,
) -> Option<Result<(PcurveNurbs, bool), cadmpeg_core::CodecError>> {
let slot = match selector {
1 | -1 => 0,
2 | -2 => 1,
_ => return None,
};
pcurve_for_selector_recursive(
ctx,
toks,
slot,
table,
&mut std::collections::HashSet::new(),
)
}
fn pcurve_for_selector_recursive(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
toks: &[Token],
slot: usize,
table: &SubtypeTable,
seen: &mut std::collections::HashSet<usize>,
) -> Option<Result<(PcurveNurbs, bool), cadmpeg_core::CodecError>> {
let _depth = propagate_resource!(ctx.enter_nested("resolve ASM pcurve reference"));
if let Some(index) = direct_subtype_reference(toks) {
if !seen.contains(&index) {
seen.insert(index);
if let Some(target) = table.span(index) {
if let Some(result) =
pcurve_for_selector_recursive(ctx, target.tokens(), slot, table, seen)
{
return Some(result);
}
}
}
}
let has_typed_construction = crate::nurbs::toks::owned_construction_subtype(toks).is_some();
if let Some(decoded) = procedural_curve_resolving_refs(ctx, toks, table) {
let decoded = match decoded {
Ok(decoded) => decoded,
Err(error) => return Some(Err(error)),
};
if let Some(pcurve) = selected_pcurve(&decoded, slot) {
let pcurve = pcurve.clone();
return Some(Ok((pcurve, false)));
}
if !matches!(&decoded.construction, ProceduralCurveConstruction::Unknown(kind) if kind == "intcurve")
{
if let Some(marker) =
crate::nurbs::toks::find_owned_intcurve_subtype(toks, "exact_int_cur")
{
let mut cur = Cur::at(toks, marker + 2);
if let Some(context) = cache_first_curve_context(ctx, &mut cur, table) {
let context = match context {
Ok(context) => context,
Err(error) => return Some(Err(error)),
};
if let Some(pcurve) =
selected_support_pcurve(&context.surfaces, &context.pcurves, slot)
{
let pcurve = pcurve.clone();
return Some(Ok((pcurve, false)));
}
}
}
return None;
}
}
if has_typed_construction {
return None;
}
(slot == 1)
.then(|| direct_pcurve_after_curve(toks))?
.map(|pcurve| Ok((pcurve, true)))
}
fn direct_subtype_reference(toks: &[Token]) -> Option<usize> {
if crate::nurbs::toks::owned_construction_subtype(toks).is_some() {
return None;
}
let mut depth = 0usize;
let mut candidate = None;
for (position, token) in toks.iter().enumerate() {
match token {
Token::SubtypeOpen => {
if depth == 0 {
let scope = crate::nurbs::toks::subtype_span(toks, position)?.tokens();
let index = match scope {
[Token::SubtypeOpen, Token::Long(index), Token::SubtypeClose]
if *index >= 0 =>
{
usize::try_from(*index).ok()
}
[Token::SubtypeOpen, Token::Ident(name), Token::Long(index), Token::SubtypeClose]
if name == "ref" && *index >= 0 =>
{
usize::try_from(*index).ok()
}
_ => None,
}?;
if candidate.replace(index).is_some() {
return None;
}
}
depth += 1;
}
Token::SubtypeClose => depth = depth.checked_sub(1)?,
_ => {}
}
}
candidate
}
fn selected_support_pcurve<'a>(
surfaces: &[SupportSlot; 2],
pcurves: &'a [Option<PcurveNurbs>; 2],
slot: usize,
) -> Option<&'a PcurveNurbs> {
match surfaces.get(slot)? {
SupportSlot::Surface(_) => pcurves.get(slot)?.as_ref(),
SupportSlot::Absent | SupportSlot::DeclaredOnly => None,
}
}
fn selected_optional_pcurve<'a>(
surfaces: &[Option<SurfaceGeometry>; 2],
pcurves: &'a [Option<PcurveNurbs>; 2],
slot: usize,
) -> Option<&'a PcurveNurbs> {
surfaces.get(slot)?.as_ref()?;
pcurves.get(slot)?.as_ref()
}
fn selected_pcurve(decoded: &DecodedProceduralCurve, slot: usize) -> Option<&PcurveNurbs> {
match &decoded.construction {
ProceduralCurveConstruction::TwoSidedOffset(context) => {
selected_optional_pcurve(&context.surfaces, &context.pcurves, slot)
}
ProceduralCurveConstruction::Intersection(context, _) => {
match context.surfaces.get(slot)? {
SupportSlot::Absent => return None,
SupportSlot::DeclaredOnly | SupportSlot::Surface(_) => {}
}
context.pcurves.get(slot)?.as_ref()
}
ProceduralCurveConstruction::ThreeSurface(context) => context.pcurves.get(slot),
ProceduralCurveConstruction::SurfaceCurve(surface_curve) => {
surface_curve.selected_pcurve(slot)
}
ProceduralCurveConstruction::Silhouette(context) => context.pcurves.get(slot),
ProceduralCurveConstruction::SurfaceOffset(offset) => match &offset.layout {
EmbeddedSurfaceOffsetLayout::ContextFirst { context, .. } => {
selected_support_pcurve(&context.surfaces, &context.pcurves, slot)
}
EmbeddedSurfaceOffsetLayout::CacheFirst { context, .. } => {
selected_support_pcurve(&context.surfaces, &context.pcurves, slot)
}
},
ProceduralCurveConstruction::Spring(context) => match &context.layout {
EmbeddedSpringLayout::CacheFirst { context, .. } => {
selected_support_pcurve(&context.surfaces, &context.pcurves, slot)
}
EmbeddedSpringLayout::ContextFirst {
supports,
first_pcurve,
second_pcurve,
..
} => matches!(supports.get(slot), Some(EmbeddedSpringSupport::Surface(_)))
.then(|| match slot {
0 => match first_pcurve.as_ref() {
EmbeddedSpringPcurve::Pcurve(pcurve) => Some(pcurve),
EmbeddedSpringPcurve::Range(_) => None,
},
1 => second_pcurve.as_ref(),
_ => None,
})
.flatten(),
},
ProceduralCurveConstruction::Deformable(embedded) => {
selected_support_pcurve(&embedded.context.surfaces, &embedded.context.pcurves, slot)
}
ProceduralCurveConstruction::Projection(context) => context.pcurves.get(slot),
ProceduralCurveConstruction::Law(context) => {
selected_support_pcurve(&context.surfaces, &context.pcurves, slot)
}
ProceduralCurveConstruction::Exact
| ProceduralCurveConstruction::Helix(_)
| ProceduralCurveConstruction::VectorOffset(_)
| ProceduralCurveConstruction::Subset(_)
| ProceduralCurveConstruction::Compound(_)
| ProceduralCurveConstruction::Unknown(_) => None,
}
}
fn direct_pcurve_after_curve(toks: &[Token]) -> Option<PcurveNurbs> {
let position = crate::nurbs::toks::owned_marker_positions(toks)?
.into_iter()
.next()?;
let (_, end) = curve_block(toks, position)?;
pcurve_block_with_end(toks, end).map(|(pcurve, _)| pcurve)
}
pub fn cacheless_procedural_curve_resolving_refs(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
toks: &[Token],
table: &SubtypeTable,
) -> Option<Result<HelixDefinition, cadmpeg_core::CodecError>> {
cacheless_procedural_curve_recursive(ctx, toks, table, &mut std::collections::HashSet::new())
}
fn cacheless_procedural_curve_recursive(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
toks: &[Token],
table: &SubtypeTable,
seen: &mut std::collections::HashSet<usize>,
) -> Option<Result<HelixDefinition, cadmpeg_core::CodecError>> {
let _depth = propagate_resource!(ctx.enter_nested("resolve ASM cacheless curve reference"));
if let Some(definition) = helix_definition(toks) {
return Some(Ok(definition));
}
for index in crate::nurbs::toks::subtype_refs(toks) {
if seen.contains(&index) {
continue;
}
seen.insert(index);
let target = table.span(index)?;
if let Some(decoded) =
cacheless_procedural_curve_recursive(ctx, target.tokens(), table, seen)
{
return Some(decoded);
}
}
None
}
fn procedural_curve_recursive(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
toks: &[Token],
table: &SubtypeTable,
seen: &mut std::collections::HashSet<usize>,
) -> Option<Result<DecodedProceduralCurve, cadmpeg_core::CodecError>> {
let _depth = propagate_resource!(ctx.enter_nested("resolve ASM procedural curve reference"));
let vector_offset = vector_offset_definition(toks);
let subset = subset_definition(toks);
let compound = compound_definition(toks);
let cache_scope = crate::nurbs::toks::cache_scope(toks)?;
let positions = crate::nurbs::toks::owned_marker_positions(cache_scope)?;
let solved = if vector_offset.is_some() || subset.is_some() || compound.is_some() {
positions
.into_iter()
.rev()
.find_map(|position| curve_block(cache_scope, position))
} else {
positions
.into_iter()
.find_map(|position| curve_block(cache_scope, position))
};
if let Some(decoded) = solved {
let (curve, end) = decoded;
let cache_fit_tolerance = match cache_scope.get(end) {
Some(Token::Double(value)) => Some(*value * LEN_TO_MM),
_ => None,
};
let construction = vector_offset
.map(ProceduralCurveConstruction::VectorOffset)
.map(Ok)
.or_else(|| subset.map(ProceduralCurveConstruction::Subset).map(Ok))
.or_else(|| {
embedded_two_sided_offset(toks)
.map(ProceduralCurveConstruction::TwoSidedOffset)
.map(Ok)
})
.or_else(|| {
embedded_intersection(ctx, toks, &curve, table).map(|result| {
result.map(|(context, flag)| {
ProceduralCurveConstruction::Intersection(context, flag)
})
})
})
.or_else(|| {
embedded_three_surface_intersection(toks)
.map(ProceduralCurveConstruction::ThreeSurface)
.map(Ok)
})
.or_else(|| {
embedded_surface_curve(ctx, toks, table)
.map(|result| result.map(ProceduralCurveConstruction::SurfaceCurve))
})
.or_else(|| {
embedded_silhouette(toks)
.map(ProceduralCurveConstruction::Silhouette)
.map(Ok)
})
.or_else(|| {
embedded_surface_offset(ctx, toks, table)
.map(|result| result.map(ProceduralCurveConstruction::SurfaceOffset))
})
.or_else(|| {
embedded_spring(ctx, toks, table)
.map(|result| result.map(ProceduralCurveConstruction::Spring))
})
.or_else(|| {
embedded_deformable(ctx, toks, table)
.map(|result| result.map(ProceduralCurveConstruction::Deformable))
})
.or_else(|| {
embedded_projection(toks)
.map(ProceduralCurveConstruction::Projection)
.map(Ok)
})
.or_else(|| {
embedded_law_curve(ctx, toks)
.map(|result| result.map(ProceduralCurveConstruction::Law))
})
.or_else(|| compound.map(ProceduralCurveConstruction::Compound).map(Ok))
.unwrap_or_else(|| {
let native_kind = crate::nurbs::toks::owned_construction_subtype(toks)
.unwrap_or_else(|| "intcurve".to_string());
if native_kind == "exact_int_cur" {
Ok(ProceduralCurveConstruction::Exact)
} else {
Ok(helix_definition(toks)
.map(ProceduralCurveConstruction::Helix)
.unwrap_or(ProceduralCurveConstruction::Unknown(native_kind)))
}
});
let construction = match construction {
Ok(construction) => construction,
Err(error) => return Some(Err(error)),
};
return Some(Ok(DecodedProceduralCurve {
curve,
construction,
cache_fit_tolerance,
}));
}
for index in crate::nurbs::toks::subtype_refs(toks) {
if seen.contains(&index) {
continue;
}
seen.insert(index);
let target = table.span(index)?;
if let Some(decoded) = procedural_curve_recursive(ctx, target.tokens(), table, seen) {
return Some(decoded);
}
}
None
}
fn embedded_deformable(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
toks: &[Token],
table: &SubtypeTable,
) -> Option<Result<EmbeddedDeformable, cadmpeg_core::CodecError>> {
let (marker, _) = crate::nurbs::toks::find_owned_subtype_marker(toks, &["defm_int_cur"])?;
let mut cur = Cur::at(toks, marker + 2);
let context = match cache_first_curve_context(ctx, &mut cur, table)? {
Ok(context) => context,
Err(error) => return Some(Err(error)),
};
let source_start = cur.pos();
let source = if let Some(curve) = embedded_base_curve_resolving_refs(&mut cur, table) {
EmbeddedDeformableSource::Curve(curve)
} else {
cur.set_pos(source_start);
(cur.take_ident()? == "intcurve").then_some(())?;
let flag = cur.take_bool()?;
let reference = cur.pos();
matches!(toks.get(reference), Some(Token::SubtypeOpen)).then_some(())?;
matches!(toks.get(reference + 1), Some(Token::Ident(name)) if name == "ref")
.then_some(())?;
let Some(Token::Long(index)) = toks.get(reference + 2) else {
return None;
};
let reference_span = crate::nurbs::toks::subtype_span(toks, reference)?.tokens();
cur.set_pos(reference + reference_span.len());
EmbeddedDeformableSource::NativeReference {
flag,
index: *index,
}
};
let source_parameter_range = [
cur.take_optional_range_value()?.value(),
cur.take_optional_range_value()?.value(),
];
let mode = cur.take_long()?;
let data = match mode {
8 => {
let mut vectors = [Vector3::new(0.0, 0.0, 0.0); 4];
for vector in &mut vectors {
let value = cur.take_vector3()?;
*vector = Vector3::new(value[0], value[1], value[2]);
}
let count = cur.take_long()?;
let count = usize::try_from(count).ok()?;
let mut parameter_pairs = Vec::new();
for _ in 0..count {
parameter_pairs.push([cur.take_f64()?, cur.take_f64()?]);
}
EmbeddedDeformableData::VectorField {
vectors,
parameter_pairs,
}
}
3 => {
let mut leading_vectors = [Vector3::new(0.0, 0.0, 0.0); 4];
for vector in &mut leading_vectors {
let value = cur.take_vector3()?;
*vector = Vector3::new(value[0], value[1], value[2]);
}
let leading_parameter = cur.take_f64()?;
let leading_flags = [cur.take_bool()?, cur.take_bool()?, cur.take_bool()?];
let value = cur.take_position()?;
let trailing_point = Point3::new(
value[0] * LEN_TO_MM,
value[1] * LEN_TO_MM,
value[2] * LEN_TO_MM,
);
let mut trailing_vectors = [Vector3::new(0.0, 0.0, 0.0); 2];
for vector in &mut trailing_vectors {
let value = cur.take_vector3()?;
*vector = Vector3::new(value[0], value[1], value[2]);
}
let frame_parameter = cur.take_f64()?;
let frame_flags = [cur.take_bool()?, cur.take_bool()?];
let parameters = [cur.take_f64()?, cur.take_f64()?, cur.take_f64()?];
let trailing_flags = [
cur.take_bool()?,
cur.take_bool()?,
cur.take_bool()?,
cur.take_bool()?,
cur.take_bool()?,
];
let trailing_parameter = cur.take_f64()?;
let trailing_value = cur.take_long()?;
EmbeddedDeformableData::Mode3 {
leading_vectors,
leading_parameter,
leading_flags,
trailing_point,
trailing_vectors,
frame_parameter,
frame_flags,
parameters,
trailing_flags,
trailing_parameter,
trailing_value,
}
}
_ => return None,
};
matches!(toks.get(cur.pos()), Some(Token::SubtypeClose)).then_some(Ok(EmbeddedDeformable {
context: Box::new(context),
source,
source_parameter_range,
data,
}))
}
fn nullable_law_surface(cur: &mut Cur<'_>) -> Option<Nullable<EmbeddedSupport>> {
let saved = cur.pos();
if cur.take_ident() == Some("null_surface") {
return Some(Nullable::Null);
}
cur.set_pos(saved);
Some(Nullable::Value(embedded_support(cur)?))
}
fn law_version_bound(cur: &mut Cur<'_>) -> Option<Nullable<f64>> {
match cur.peek()? {
Token::False => {
cur.bump();
Some(Nullable::Null)
}
Token::True => {
cur.bump();
cur.take_f64().map(Nullable::Value)
}
_ => None,
}
}
fn embedded_law_curve(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
toks: &[Token],
) -> Option<Result<EmbeddedLawCurve, cadmpeg_core::CodecError>> {
let marker = crate::nurbs::toks::find_owned_intcurve_subtype(toks, "law_int_cur")?;
let mut cur = Cur::at(toks, marker + 2);
let stamp_start = cur.pos();
let stamp = matches!(cur.peek(), Some(Token::Long(_)))
.then(|| {
let stamp = cur.take_long()?;
let post_enum = cur.take_enum()?;
Some((stamp, post_enum))
})
.flatten();
if stamp.is_none() {
cur.set_pos(stamp_start);
}
let (_, solved_end) = curve_block(toks, cur.pos())?;
cur.set_pos(solved_end);
cur.take_f64()?;
let supports = [
nullable_law_surface(&mut cur)?.value(),
nullable_law_surface(&mut cur)?.value(),
];
let mut pcurves = [
nullable_embedded_pcurve(&mut cur)?.value(),
nullable_embedded_pcurve(&mut cur)?.value(),
];
for (pcurve, support) in pcurves.iter_mut().zip(&supports) {
if let (Some(pcurve), Some(support)) = (pcurve, support) {
support.chart.normalize(pcurve)?;
}
}
let surfaces = supports.map(|support| support.map(|support| support.surface));
let layout = if let Some((stamp, post_enum)) = stamp {
EmbeddedLawCurveLayout::Version {
stamp,
post_enum,
parameter_range: [
law_version_bound(&mut cur)?.value(),
law_version_bound(&mut cur)?.value(),
],
}
} else {
EmbeddedLawCurveLayout::Legacy([cur.take_range_value()?, cur.take_range_value()?])
};
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
let extension = cur.take_long()?;
let primary = propagate_resource!(law_formula(ctx, &mut cur)?);
let count = usize::try_from(cur.take_long()?).ok()?;
if count > 100_000 {
return None;
}
let mut additional = Vec::new();
for _ in 0..count {
additional.push(propagate_resource!(law_formula(ctx, &mut cur)?));
}
Some(Ok(EmbeddedLawCurve {
surfaces: surfaces.map(|surface| surface.map_or(SupportSlot::Absent, SupportSlot::Surface)),
pcurves,
discontinuities,
layout,
extension,
primary,
additional,
}))
}
fn embedded_spring(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
toks: &[Token],
table: &SubtypeTable,
) -> Option<Result<EmbeddedSpring, cadmpeg_core::CodecError>> {
let marker = crate::nurbs::toks::find_owned_intcurve_subtype(toks, "spring_int_cur")?;
let mut cur = Cur::at(toks, marker + 2);
if matches!(cur.peek(), Some(Token::Long(_))) {
let context = match cache_first_curve_context(ctx, &mut cur, table)? {
Ok(context) => context,
Err(error) => return Some(Err(error)),
};
let direction = cur.take_enum()?;
return Some(Ok(EmbeddedSpring {
layout: EmbeddedSpringLayout::CacheFirst {
context: Box::new(context),
},
direction,
}));
}
let mut take_support = || -> Option<Result<_, cadmpeg_core::CodecError>> {
let saved = cur.pos();
if cur.take_ident() == Some("null_surface") {
Some(Ok((
EmbeddedSpringSupport::Ranges([
[cur.take_range_value()?, cur.take_range_value()?],
[cur.take_range_value()?, cur.take_range_value()?],
]),
SupportChart::Canonical,
)))
} else {
cur.set_pos(saved);
let EmbeddedSupport { surface, chart, .. } = embedded_support(&mut cur)?;
Some(Ok((EmbeddedSpringSupport::Surface(surface), chart)))
}
};
let [(first_support, first_chart), (second_support, second_chart)] = [
propagate_resource!(take_support()?),
propagate_resource!(take_support()?),
];
let saved = cur.pos();
let mut first_pcurve = if cur.take_ident() == Some("nullbs") {
EmbeddedSpringPcurve::Range([cur.take_range_value()?, cur.take_range_value()?])
} else {
cur.set_pos(saved);
let (pcurve, end) = pcurve_block_with_end(toks, cur.pos())?;
cur.set_pos(end);
EmbeddedSpringPcurve::Pcurve(pcurve)
};
let saved = cur.pos();
let second_pcurve = if cur.take_ident() == Some("nullbs") {
None
} else {
cur.set_pos(saved);
let (pcurve, end) = pcurve_block_with_end(toks, cur.pos())?;
cur.set_pos(end);
Some(pcurve)
};
if let EmbeddedSpringPcurve::Pcurve(pcurve) = &mut first_pcurve {
first_chart.normalize(pcurve)?;
}
let mut second_pcurve = second_pcurve;
if let Some(pcurve) = &mut second_pcurve {
second_chart.normalize(pcurve)?;
}
let parameter_range = [cur.take_range_value()?, cur.take_range_value()?];
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
let discontinuity_flag = cur.take_bool()?;
let direction = cur.take_enum()?;
Some(Ok(EmbeddedSpring {
layout: EmbeddedSpringLayout::ContextFirst {
supports: Box::new([first_support, second_support]),
first_pcurve: Box::new(first_pcurve),
second_pcurve,
parameter_range,
discontinuities,
discontinuity_flag,
},
direction,
}))
}
pub struct SpringPatchLayout {
pub parameter_range: [usize; 2],
pub discontinuities: [Vec<usize>; 3],
pub discontinuity_flag: usize,
pub direction: usize,
}
pub fn spring_patch_layout(bytes: &[u8], int_width: RefWidth) -> Option<SpringPatchLayout> {
let (marker, name_len) = find_owned_intcurve_subtype(bytes, b"spring_int_cur", int_width)?;
let mut position = marker + name_len + 3;
for _ in 0..2 {
let saved = position;
if take_native_ident(bytes, &mut position).as_deref() == Some("null_surface") {
for _ in 0..4 {
take_double_payload(bytes, &mut position)?;
}
} else {
position = saved;
decode_embedded_surface(bytes, &mut position, int_width)?;
}
}
let saved = position;
if take_native_ident(bytes, &mut position).as_deref() == Some("nullbs") {
take_double_payload(bytes, &mut position)?;
take_double_payload(bytes, &mut position)?;
} else {
position = decode_pcurve_block_with_end(bytes, saved, int_width)?.1;
}
let saved = position;
if take_native_ident(bytes, &mut position).as_deref() != Some("nullbs") {
position = decode_pcurve_block_with_end(bytes, saved, int_width)?.1;
}
let parameter_range = [
take_double_payload(bytes, &mut position)?,
take_double_payload(bytes, &mut position)?,
];
let discontinuities = [
take_float_array_payloads(bytes, &mut position, int_width)?,
take_float_array_payloads(bytes, &mut position, int_width)?,
take_float_array_payloads(bytes, &mut position, int_width)?,
];
let discontinuity_flag = position;
take_bool(bytes, &mut position)?;
let direction = position;
take_tagged_int(bytes, &mut position, 0x15, int_width)?;
Some(SpringPatchLayout {
parameter_range,
discontinuities,
discontinuity_flag,
direction,
})
}
pub struct RollingBallPatchLayout {
pub radii: [usize; 2],
}
pub struct ExtrusionPatchLayout {
pub parameter_interval: [usize; 2],
pub direction: usize,
pub native_position: usize,
}
pub struct HelixPatchLayout {
pub angle_range: [usize; 2],
pub frame_vectors: [usize; 4],
pub apex_factor: usize,
pub axis: usize,
}
pub struct VectorOffsetPatchLayout {
pub parameter_range: [usize; 2],
pub offset: usize,
}
pub struct SubsetPatchLayout {
pub parameter_range: [usize; 2],
}
pub struct CompoundPatchLayout {
pub parameters: Vec<usize>,
pub component_parameters: Vec<usize>,
}
pub fn compound_patch_layout(bytes: &[u8], int_width: RefWidth) -> Option<CompoundPatchLayout> {
let name = b"comp_int_cur";
let marker = find_owned_subtype_marker(bytes, &[name], int_width).map(|(marker, _)| marker)?;
subtype_span(bytes, marker, int_width)?;
let mut position = marker + name.len() + 3;
let parameters = take_float_array_payloads(bytes, &mut position, int_width)?;
let component_count =
usize::try_from(take_tagged_int(bytes, &mut position, 0x04, int_width)?).ok()?;
if component_count == 0 {
return None;
}
let mut component_parameters = Vec::new();
for _ in 0..component_count {
component_parameters.push(take_double_payload(bytes, &mut position)?);
}
Some(CompoundPatchLayout {
parameters,
component_parameters,
})
}
pub fn subset_patch_layout(bytes: &[u8], int_width: RefWidth) -> Option<SubsetPatchLayout> {
let name = b"subset_int_cur";
let marker = find_owned_subtype_marker(bytes, &[name], int_width).map(|(marker, _)| marker)?;
subtype_span(bytes, marker, int_width)?;
let mut position = marker + name.len() + 3;
position = decode_curve_block(bytes, position, int_width)?.end();
let parameter_range = [
take_double_payload(bytes, &mut position)?,
take_double_payload(bytes, &mut position)?,
];
Some(SubsetPatchLayout { parameter_range })
}
pub fn vector_offset_patch_layout(
bytes: &[u8],
int_width: RefWidth,
) -> Option<VectorOffsetPatchLayout> {
let name = b"offset_int_cur";
let marker = find_owned_subtype_marker(bytes, &[name], int_width).map(|(marker, _)| marker)?;
subtype_span(bytes, marker, int_width)?;
let mut position = marker + name.len() + 3;
take_bool(bytes, &mut position)?;
position = decode_curve_block(bytes, position, int_width)?.end();
let parameter_range = [
take_double_payload(bytes, &mut position)?,
take_double_payload(bytes, &mut position)?,
];
let offset = position + 1;
take_native_vec3(bytes, &mut position, 0x14)?;
Some(VectorOffsetPatchLayout {
parameter_range,
offset,
})
}
pub fn helix_patch_layout(bytes: &[u8], int_width: RefWidth) -> Option<HelixPatchLayout> {
let name = b"helix_int_cur";
let marker = find_owned_subtype_marker(bytes, &[name], int_width).map(|(marker, _)| marker)?;
subtype_span(bytes, marker, int_width)?;
let mut position = marker + name.len() + 3;
let current_layout = take_optional_helix_revision(bytes, &mut position, int_width)?;
let take_range_payload = |position: &mut usize| {
if matches!(bytes.get(*position), Some(0x0a | 0x0b)) {
*position += 1;
}
take_double_payload(bytes, position)
};
let angle_range = [
take_range_payload(&mut position)?,
take_range_payload(&mut position)?,
];
let mut frame_vectors = [0usize; 4];
let frame_tags = if current_layout {
[0x13, 0x14, 0x14, 0x14]
} else {
[0x13; 4]
};
for (offset, tag) in frame_vectors.iter_mut().zip(frame_tags) {
*offset = position + 1;
take_native_vec3(bytes, &mut position, tag)?;
}
let apex_factor = take_double_payload(bytes, &mut position)?;
let axis = position + 1;
take_native_vec3(bytes, &mut position, 0x14)?;
Some(HelixPatchLayout {
angle_range,
frame_vectors,
apex_factor,
axis,
})
}
pub fn extrusion_patch_layout(bytes: &[u8], int_width: RefWidth) -> Option<ExtrusionPatchLayout> {
let names: [&[u8]; 2] = [b"cyl_spl_sur", b"cylsur"];
let (start, name_len) = find_owned_subtype_marker(bytes, &names, int_width)
.map(|(start, name)| (start, name.len()))?;
subtype_span(bytes, start, int_width)?;
let mut position = start + name_len + 3;
let parameter_interval = [
take_double_payload(bytes, &mut position)?,
take_double_payload(bytes, &mut position)?,
];
let direction = position + 1;
take_native_vec3(bytes, &mut position, 0x14)?;
let native_position = position + 1;
take_native_vec3(bytes, &mut position, 0x13)?;
Some(ExtrusionPatchLayout {
parameter_interval,
direction,
native_position,
})
}
pub fn rolling_ball_patch_layout(
bytes: &[u8],
int_width: RefWidth,
) -> Option<Result<RollingBallPatchLayout, cadmpeg_core::decode::ResourceLimit>> {
let names: [&[u8]; 6] = [
b"rb_blend_spl_sur",
b"rbblnsur",
b"pipe_spl_sur",
b"pipesur",
b"sss_blend_spl_sur",
b"sssblndsur",
];
let (start, name_len) = find_owned_subtype_marker(bytes, &names, int_width)
.map(|(start, name)| (start, name.len()))?;
let span = subtype_span(bytes, start, int_width)?.bytes();
let payload_start = name_len + 3;
let radii = (|| {
let mut position = payload_start;
take_tagged_int(span, &mut position, 0x04, int_width)?;
for _ in 0..2 {
match decode_rolling_ball_side(span, &mut position, int_width)? {
Ok(_) => {}
Err(limit) => return Some(Err(limit)),
}
}
position = decode_curve_block(span, position, int_width)?.end();
Some(Ok([
start + take_double_payload(span, &mut position)?,
start + take_double_payload(span, &mut position)?,
]))
})()
.or_else(|| {
let mut position = payload_start;
for _ in 0..2 {
take_native_string(span, &mut position, int_width)?;
let support_kind = take_native_ident(span, &mut position)?;
if !matches!(support_kind.as_str(), "plane" | "sphere" | "cone" | "torus") {
return None;
}
position = decode_surface_block(span, position, int_width)?.end();
}
position = decode_curve_block(span, position, int_width)?.end();
Some(Ok([
start + take_double_payload(span, &mut position)?,
start + take_double_payload(span, &mut position)?,
]))
})?;
Some(radii.map(|radii| RollingBallPatchLayout { radii }))
}
pub(super) fn embedded_base_curve_resolving_refs(
cur: &mut Cur<'_>,
table: &SubtypeTable,
) -> Option<NurbsCurve> {
let toks = cur.toks();
if let Some(decoded) = curve_block(toks, cur.pos()) {
let (curve, end) = decoded;
cur.set_pos(end);
return Some(curve);
}
let saved = cur.pos();
if matches!(cur.peek(), Some(Token::True | Token::False)) {
cur.take_bool()?;
let reference = cur.pos();
if matches!(toks.get(reference), Some(Token::SubtypeOpen)) {
let scope = crate::nurbs::toks::subtype_span(toks, reference)?;
if let Some(curve) = owned_curve_cache_resolving_refs(scope, table) {
cur.set_pos(reference + scope.tokens().len());
return Some(curve);
}
}
cur.set_pos(saved);
}
match cur.take_ident()? {
"straight" => {
let origin = cur.take_position()?;
let direction = cur.take_vector3()?;
let start = Point3::new(
origin[0] * LEN_TO_MM,
origin[1] * LEN_TO_MM,
origin[2] * LEN_TO_MM,
);
let end = Point3::new(
(origin[0] + direction[0]) * LEN_TO_MM,
(origin[1] + direction[1]) * LEN_TO_MM,
(origin[2] + direction[2]) * LEN_TO_MM,
);
NurbsCurve::new(
1,
vec![0.0, 0.0, 1.0, 1.0],
cadmpeg_ir::geometry::nurbs::NurbsPoles3::Polynomial {
points: vec![start, end],
},
false,
)
.ok()
}
"ellipse" => {
let center = cur.take_position()?;
let normal = cur.take_vector3()?;
let major = cur.take_vector3()?;
let ratio = cur.take_f64()?;
ellipse_to_nurbs(center, normal, major, ratio)
}
"degenerate_curve" => {
let point = cur.take_position()?;
let at = Point3::new(
point[0] * LEN_TO_MM,
point[1] * LEN_TO_MM,
point[2] * LEN_TO_MM,
);
NurbsCurve::new(
1,
vec![0.0, 0.0, 1.0, 1.0],
cadmpeg_ir::geometry::nurbs::NurbsPoles3::Polynomial {
points: vec![at, at],
},
false,
)
.ok()
}
"intcurve" => {
cur.take_bool()?;
let reference = cur.pos();
let compact_ref = matches!(toks.get(reference), Some(Token::SubtypeOpen))
&& matches!(toks.get(reference + 1), Some(Token::Ident(name)) if name == "ref")
&& matches!(toks.get(reference + 2), Some(Token::Long(_)));
if !compact_ref {
if matches!(toks.get(reference), Some(Token::SubtypeOpen)) {
let scope = crate::nurbs::toks::subtype_span(toks, reference)?;
let curve = owned_curve_cache_resolving_refs(scope, table)?;
cur.set_pos(reference + scope.tokens().len());
return Some(curve);
}
cur.set_pos(saved);
return None;
}
let Some(Token::Long(index)) = toks.get(reference + 2) else {
return None;
};
let index = usize::try_from(*index).ok()?;
let reference_span = crate::nurbs::toks::subtype_span(toks, reference)?.tokens();
cur.set_pos(reference + reference_span.len());
table
.span(index)
.and_then(|target| owned_curve_cache_resolving_refs(target, table))
}
_ => {
cur.set_pos(saved);
None
}
}
}
fn embedded_surface_offset(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
toks: &[Token],
table: &SubtypeTable,
) -> Option<Result<EmbeddedSurfaceOffset, cadmpeg_core::CodecError>> {
let marker = crate::nurbs::toks::find_owned_intcurve_subtype(toks, "off_surf_int_cur")?;
let mut cur = Cur::at(toks, marker + 2);
if matches!(cur.peek(), Some(Token::Long(_))) {
let context = match cache_first_curve_context(ctx, &mut cur, table)? {
Ok(context) => context,
Err(error) => return Some(Err(error)),
};
let base_u_range = [
cur.take_optional_range_value()?.value()?,
cur.take_optional_range_value()?.value()?,
];
let base_v_range = [
cur.take_optional_range_value()?.value()?,
cur.take_optional_range_value()?.value()?,
];
let base = embedded_base_curve_resolving_refs(&mut cur, table)?;
let base_endpoints = [
cur.take_optional_range_value()?.value(),
cur.take_optional_range_value()?.value(),
];
let base_range = [
cur.take_optional_range_value()?.value()?,
cur.take_optional_range_value()?.value()?,
];
return Some(Ok(EmbeddedSurfaceOffset {
layout: EmbeddedSurfaceOffsetLayout::CacheFirst {
context: Box::new(context),
base_endpoints,
},
base_u_range,
base_v_range,
base,
base_range,
distance: cur.take_f64()? * LEN_TO_MM,
shift: cur.take_f64()?,
scale: cur.take_f64()?,
}));
}
let RequiredSupportPair { surfaces, pcurves } = required_support_pair(&mut cur)?;
let parameter_range = [cur.take_range_value()?, cur.take_range_value()?];
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
let discontinuity_flag = cur.take_bool()?;
let base_u_range = [cur.take_range_value()?, cur.take_range_value()?];
let base_v_range = [cur.take_range_value()?, cur.take_range_value()?];
let (base, base_end) = curve_block(toks, cur.pos())?;
cur.set_pos(base_end);
let base_range = [cur.take_range_value()?, cur.take_range_value()?];
Some(Ok(EmbeddedSurfaceOffset {
layout: EmbeddedSurfaceOffsetLayout::ContextFirst {
context: Box::new(EmbeddedIntersection {
surfaces: surfaces.map(SupportSlot::Surface),
pcurves: pcurves.map(Some),
parameter_range,
discontinuities,
}),
discontinuity_flag,
},
base_u_range,
base_v_range,
base,
base_range,
distance: cur.take_f64()? * LEN_TO_MM,
shift: cur.take_f64()?,
scale: cur.take_f64()?,
}))
}
pub struct SurfaceOffsetPatchLayout {
pub parameter_range: [usize; 2],
pub discontinuities: [Vec<usize>; 3],
pub discontinuity_flag: usize,
pub base_u_range: [usize; 2],
pub base_v_range: [usize; 2],
pub base_range: [usize; 2],
pub distance: usize,
pub shift: usize,
pub scale: usize,
}
pub fn surface_offset_patch_layout(
bytes: &[u8],
int_width: RefWidth,
) -> Option<SurfaceOffsetPatchLayout> {
let (marker, name_len) = find_owned_intcurve_subtype(bytes, b"off_surf_int_cur", int_width)?;
let mut position = marker + name_len + 3;
decode_embedded_surface(bytes, &mut position, int_width)?;
decode_embedded_surface(bytes, &mut position, int_width)?;
position = decode_pcurve_block_with_end(bytes, position, int_width)?.1;
position = decode_pcurve_block_with_end(bytes, position, int_width)?.1;
let parameter_range = [
take_double_payload(bytes, &mut position)?,
take_double_payload(bytes, &mut position)?,
];
let discontinuities = [
take_float_array_payloads(bytes, &mut position, int_width)?,
take_float_array_payloads(bytes, &mut position, int_width)?,
take_float_array_payloads(bytes, &mut position, int_width)?,
];
let discontinuity_flag = position;
take_bool(bytes, &mut position)?;
let base_u_range = [
take_double_payload(bytes, &mut position)?,
take_double_payload(bytes, &mut position)?,
];
let base_v_range = [
take_double_payload(bytes, &mut position)?,
take_double_payload(bytes, &mut position)?,
];
position = decode_curve_block(bytes, position, int_width)?.end();
let base_range = [
take_double_payload(bytes, &mut position)?,
take_double_payload(bytes, &mut position)?,
];
let distance = take_double_payload(bytes, &mut position)?;
let shift = take_double_payload(bytes, &mut position)?;
let scale = take_double_payload(bytes, &mut position)?;
Some(SurfaceOffsetPatchLayout {
parameter_range,
discontinuities,
discontinuity_flag,
base_u_range,
base_v_range,
base_range,
distance,
shift,
scale,
})
}
const ZERO_DRAFT_FACTOR: cadmpeg_ir::scalar::FiniteReal = cadmpeg_ir::scalar::FiniteReal::ZERO;
fn embedded_silhouette(toks: &[Token]) -> Option<EmbeddedSilhouette> {
use cadmpeg_ir::geometry::SilhouetteKind;
let names = [
("silh_int_cur", SilhouetteKind::Standard {}),
("para_silh_int_cur", SilhouetteKind::Parametric {}),
("parasil", SilhouetteKind::Parametric {}),
(
"taper_silh_int_cur",
SilhouetteKind::Taper {
draft_factor: ZERO_DRAFT_FACTOR,
},
),
];
let candidates: Vec<&str> = names.iter().map(|(name, _)| *name).collect();
let (marker, name) = crate::nurbs::toks::find_owned_subtype_marker(toks, &candidates)?;
let mut silhouette = names
.iter()
.find_map(|(candidate, silhouette)| (*candidate == name).then(|| silhouette.clone()))?;
let mut cur = Cur::at(toks, marker + 2);
let RequiredSupportPair { surfaces, pcurves } = required_support_pair(&mut cur)?;
let parameter_range = [cur.take_range_value()?, cur.take_range_value()?];
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
let cast_surface = embedded_surface(&mut cur)?;
let light = cur.take_vector3()?;
let light_direction = normalized(light)?;
if matches!(silhouette, SilhouetteKind::Taper { .. }) {
silhouette = SilhouetteKind::Taper {
draft_factor: cadmpeg_ir::scalar::FiniteReal::new(cur.take_f64()?)?,
};
}
Some(EmbeddedSilhouette {
surfaces,
pcurves,
parameter_range,
discontinuities,
silhouette,
cast_surface,
light_direction,
})
}
pub struct SilhouettePatchLayout {
pub light_direction: usize,
pub draft_factor: Option<usize>,
}
pub fn silhouette_patch_layout(
bytes: &[u8],
int_width: RefWidth,
silhouette: &cadmpeg_ir::geometry::SilhouetteKind,
) -> Option<SilhouettePatchLayout> {
use cadmpeg_ir::geometry::SilhouetteKind;
let (names, tapered): (&[&[u8]], bool) = match silhouette {
SilhouetteKind::Standard {} => (&[b"silh_int_cur"], false),
SilhouetteKind::Parametric {} => (&[b"para_silh_int_cur", b"parasil"], false),
SilhouetteKind::Taper { .. } => (&[b"taper_silh_int_cur"], true),
};
let (marker, name) = find_owned_subtype_marker(bytes, names, int_width)?;
let mut position = marker + name.len() + 3;
decode_embedded_surface(bytes, &mut position, int_width)?;
decode_embedded_surface(bytes, &mut position, int_width)?;
position = decode_pcurve_block_with_end(bytes, position, int_width)?.1;
position = decode_pcurve_block_with_end(bytes, position, int_width)?.1;
take_double_payload(bytes, &mut position)?;
take_double_payload(bytes, &mut position)?;
for _ in 0..3 {
take_float_array_payloads(bytes, &mut position, int_width)?;
}
decode_embedded_surface(bytes, &mut position, int_width)?;
(*bytes.get(position)? == 0x14).then_some(())?;
let light_direction = position + 1;
bytes.get(light_direction..light_direction + 24)?;
position = light_direction + 24;
let draft_factor = if tapered {
Some(take_double_payload(bytes, &mut position)?)
} else {
None
};
Some(SilhouettePatchLayout {
light_direction,
draft_factor,
})
}
fn embedded_surface_curve(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
toks: &[Token],
table: &SubtypeTable,
) -> Option<Result<EmbeddedSurfaceCurve, cadmpeg_core::CodecError>> {
use cadmpeg_ir::geometry::SurfaceCurveFamilyKind;
let names = [
("blend_int_cur", SurfaceCurveFamilyKind::Blend),
("bldcur", SurfaceCurveFamilyKind::Blend),
("surf_int_cur", SurfaceCurveFamilyKind::SurfaceConstrained),
("surfcur", SurfaceCurveFamilyKind::SurfaceConstrained),
("par_int_cur", SurfaceCurveFamilyKind::Parametric),
("parcur", SurfaceCurveFamilyKind::Parametric),
("skin_int_cur", SurfaceCurveFamilyKind::Skin),
("d5c2_cur", SurfaceCurveFamilyKind::Skin),
];
let candidates: Vec<&str> = names.iter().map(|(name, _)| *name).collect();
let (marker, name) = crate::nurbs::toks::find_owned_subtype_marker(toks, &candidates)?;
let family = names
.iter()
.find_map(|(candidate, family)| (*candidate == name).then_some(*family))?;
let position = marker + 2;
context_first_surface_curve(toks, position, family)
.map(Ok)
.or_else(|| cache_first_surface_curve(ctx, toks, position, family, table))
}
pub fn decode_par_int_cur_isoline(
scope: &[u8],
int_width: RefWidth,
) -> Option<Result<NurbsCurve, cadmpeg_core::decode::ResourceLimit>> {
let names: [&[u8]; 2] = [b"par_int_cur", b"parcur"];
let (start, name) = find_owned_subtype_marker(scope, &names, int_width)?;
let mut position = start + name.len() + 3;
(take_tagged_int(scope, &mut position, 0x04, int_width)? > 0).then_some(())?;
(take_tagged_int(scope, &mut position, 0x15, int_width)? == 2).then_some(())?;
take_range_value(scope, &mut position)?;
take_range_value(scope, &mut position)?;
take_tagged_int(scope, &mut position, 0x15, int_width)?;
let supports = [
decode_optional_rolling_ball_surface(scope, &mut position, int_width)?
.value()
.map(|support| support.surface),
decode_optional_rolling_ball_surface(scope, &mut position, int_width)?
.value()
.map(|support| support.surface),
];
let pcurves = [
decode_nullable_embedded_pcurve(scope, &mut position, int_width)?.value(),
decode_nullable_embedded_pcurve(scope, &mut position, int_width)?.value(),
];
let occupied: Vec<usize> = (0..2)
.filter(|slot| supports[*slot].is_some() || pcurves[*slot].is_some())
.collect();
let [slot] = occupied.as_slice() else {
return None;
};
let (Some(SurfaceGeometry::Solved(SolvedSurfaceGeometry::Nurbs(support))), Some(pcurve)) =
(&supports[*slot], &pcurves[*slot])
else {
return None;
};
surface_isoline_along(
cadmpeg_ir::eval::admission::EvaluationAdmission::Standard,
support,
pcurve,
)
.transpose()
}
pub(super) fn par_int_cur_isoline(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
scope: &[Token],
reference_context: Option<&SubtypeTable>,
) -> Option<Result<NurbsCurve, cadmpeg_core::CodecError>> {
let (start, _) =
crate::nurbs::toks::find_owned_subtype_marker(scope, &["par_int_cur", "parcur"])?;
let mut cur = Cur::at(scope, start + 2);
(cur.take_long()? > 0).then_some(())?;
(cur.take_enum()? == 2).then_some(())?;
cur.take_range_value()?;
cur.take_range_value()?;
cur.take_enum()?;
let supports = [
propagate_resource!(optional_rolling_ball_surface(
ctx,
&mut cur,
reference_context
)?)
.value()
.map(|(support, _)| support.surface),
propagate_resource!(optional_rolling_ball_surface(
ctx,
&mut cur,
reference_context
)?)
.value()
.map(|(support, _)| support.surface),
];
let pcurves = [
nullable_embedded_pcurve(&mut cur)?.value(),
nullable_embedded_pcurve(&mut cur)?.value(),
];
let occupied: Vec<usize> = (0..2)
.filter(|slot| supports[*slot].is_some() || pcurves[*slot].is_some())
.collect();
let [slot] = occupied.as_slice() else {
return None;
};
let (Some(SurfaceGeometry::Solved(SolvedSurfaceGeometry::Nurbs(support))), Some(pcurve)) =
(&supports[*slot], &pcurves[*slot])
else {
return None;
};
surface_isoline_along(ctx.into(), support, pcurve)
.transpose()
.map(|result| result.map_err(Into::into))
}
fn surface_isoline_along(
admission: cadmpeg_ir::eval::admission::EvaluationAdmission<'_, '_>,
support: &cadmpeg_ir::geometry::nurbs::NurbsSurface,
pcurve: &PcurveNurbs,
) -> Result<Option<NurbsCurve>, cadmpeg_core::decode::ResourceLimit> {
use cadmpeg_ir::eval::IsolineDirection;
let Some((direction, at)) = (|| {
let poles = pcurve.pole_rows();
(pcurve.degree() == 1 && poles.count() == 2 && poles.weight_at(0).is_none())
.then_some(())?;
let start = poles.point_at(0)?.get();
let end = poles.point_at(1)?.get();
let domain = [*pcurve.knots().first()?, *pcurve.knots().last()?];
let u_domain = [*support.u_knots().first()?, *support.u_knots().last()?];
let v_domain = [*support.v_knots().first()?, *support.v_knots().last()?];
let (direction, at, free_domain, travel) = if agree(start.u, end.u, width(u_domain)) {
(
IsolineDirection::ConstantU,
start.u,
v_domain,
[start.v, end.v],
)
} else if agree(start.v, end.v, width(v_domain)) {
(
IsolineDirection::ConstantV,
start.v,
u_domain,
[start.u, end.u],
)
} else {
return None;
};
let scale = width(free_domain);
(agree(travel[0], domain[0], scale)
&& agree(travel[1], domain[1], scale)
&& agree(free_domain[0], domain[0], scale)
&& agree(free_domain[1], domain[1], scale))
.then_some(())?;
Some((direction, at))
})() else {
return Ok(None);
};
cadmpeg_ir::eval::nurbs_surface_isoline(admission, support, direction, at)
}
fn width(domain: [f64; 2]) -> f64 {
(domain[1] - domain[0]).abs()
}
fn agree(left: f64, right: f64, scale: f64) -> bool {
(left - right).abs() <= EPS_PARAMETER_AGREEMENT * scale
}
pub struct CacheFirstCurveContext {
pub(super) form: cadmpeg_ir::geometry::CacheFirstCurveForm,
pub(super) surfaces: [SupportSlot; 2],
pub(super) pcurves: [Option<PcurveNurbs>; 2],
discontinuities: [Vec<f64>; 3],
}
impl CacheFirstCurveContext {
pub(crate) fn into_intersection(
self,
domain: [f64; 2],
) -> (
EmbeddedIntersection,
cadmpeg_ir::geometry::CacheFirstCurveForm,
) {
let parameter_range =
std::array::from_fn(|index| self.form.solved_range[index].unwrap_or(domain[index]));
(
EmbeddedIntersection {
surfaces: self.surfaces,
pcurves: self.pcurves,
parameter_range,
discontinuities: self.discontinuities,
},
self.form,
)
}
}
fn cache_first_curve_context(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
cur: &mut Cur<'_>,
table: &SubtypeTable,
) -> Option<Result<CacheFirstCurveContext, cadmpeg_core::CodecError>> {
let revision = PositiveI64::new(cur.take_long()?)?;
let cache_enum = cur.take_enum()?;
let cache = match cache_enum {
0 => {
let (_, end) = curve_block(cur.toks(), cur.pos())?;
cur.set_pos(end);
cadmpeg_ir::geometry::RevisionCacheForm::SolvedCache {
fit_tolerance: cadmpeg_ir::geometry::FitTolerance::try_new(
cur.take_f64()? * LEN_TO_MM,
)
.ok()?,
}
}
2 => cadmpeg_ir::geometry::RevisionCacheForm::Parameterization(
cadmpeg_ir::geometry::CacheFirstCurveParameterization {
interval: [
cur.take_optional_range_value()?.value(),
cur.take_optional_range_value()?.value(),
],
closed_form: cur.take_enum()?,
},
),
_ => return None,
};
let EmbeddedSurfaceWithBounds {
surface: first_surface,
chart: first_chart,
bounds: first_bounds,
} = match optional_embedded_surface_with_bounds(ctx, cur, table)? {
Ok(surface) => surface,
Err(error) => return Some(Err(error)),
};
let EmbeddedSurfaceWithBounds {
surface: second_surface,
chart: second_chart,
bounds: second_bounds,
} = match optional_embedded_surface_with_bounds(ctx, cur, table)? {
Ok(surface) => surface,
Err(error) => return Some(Err(error)),
};
let mut pcurves = [
nullable_embedded_pcurve(cur)?.value(),
nullable_embedded_pcurve(cur)?.value(),
];
for (pcurve, chart) in pcurves.iter_mut().zip([first_chart, second_chart]) {
if let Some(pcurve) = pcurve {
chart.normalize(pcurve)?;
}
}
let solved_range = [
cur.take_optional_range_value()?.value(),
cur.take_optional_range_value()?.value(),
];
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
let extension = cur.take_long()?;
Some(Ok(CacheFirstCurveContext {
form: cadmpeg_ir::geometry::CacheFirstCurveForm {
revision,
cache,
support_bounds: [first_bounds, second_bounds],
solved_range,
extension,
},
surfaces: [first_surface, second_surface],
pcurves,
discontinuities,
}))
}
fn context_first_surface_curve(
toks: &[Token],
position: usize,
family: cadmpeg_ir::geometry::SurfaceCurveFamilyKind,
) -> Option<EmbeddedSurfaceCurve> {
let mut cur = Cur::at(toks, position);
let RequiredSupportPair { surfaces, pcurves } = required_support_pair(&mut cur)?;
let parameter_range = [cur.take_range_value()?, cur.take_range_value()?];
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
embedded_surface_curve_from_parts(
family,
EmbeddedSurfaceCurveLayout::ContextFirst(EmbeddedIntersection {
surfaces: surfaces.map(SupportSlot::Surface),
pcurves: pcurves.map(Some),
parameter_range,
discontinuities,
}),
)
}
fn cache_first_surface_curve(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
toks: &[Token],
position: usize,
family: cadmpeg_ir::geometry::SurfaceCurveFamilyKind,
table: &SubtypeTable,
) -> Option<Result<EmbeddedSurfaceCurve, cadmpeg_core::CodecError>> {
let mut cur = Cur::at(toks, position);
let context = match cache_first_curve_context(ctx, &mut cur, table)? {
Ok(context) => context,
Err(error) => return Some(Err(error)),
};
let flag = cur.take_bool()?;
let second_flag = matches!(cur.peek(), Some(Token::True | Token::False))
.then(|| cur.take_bool())
.flatten();
embedded_surface_curve_from_parts(
family,
EmbeddedSurfaceCurveLayout::CacheFirst {
context,
flags: (flag, second_flag),
},
)
.map(Ok)
}
fn embedded_surface_curve_from_parts(
family: cadmpeg_ir::geometry::SurfaceCurveFamilyKind,
layout: EmbeddedSurfaceCurveLayout<(bool, Option<bool>)>,
) -> Option<EmbeddedSurfaceCurve> {
use cadmpeg_ir::geometry::{ParametricSurfaceCurveFlags, SurfaceCurveFamilyKind};
let single = |(flag, second): (bool, Option<bool>)| second.is_none().then_some(flag);
Some(match family {
SurfaceCurveFamilyKind::Blend => EmbeddedSurfaceCurve::Blend(layout.try_map_flags(single)?),
SurfaceCurveFamilyKind::SurfaceConstrained => {
EmbeddedSurfaceCurve::SurfaceConstrained(layout.try_map_flags(single)?)
}
SurfaceCurveFamilyKind::Parametric => {
EmbeddedSurfaceCurve::Parametric(layout.try_map_flags(|(flag, second_flag)| {
Some(ParametricSurfaceCurveFlags { flag, second_flag })
})?)
}
SurfaceCurveFamilyKind::Skin => EmbeddedSurfaceCurve::Skin(layout.try_map_flags(single)?),
})
}
pub struct SurfaceCurvePatchLayout {
pub parameter_range: [usize; 2],
pub discontinuities: [Vec<usize>; 3],
}
pub fn surface_curve_patch_layout(
bytes: &[u8],
int_width: RefWidth,
family: cadmpeg_ir::geometry::SurfaceCurveFamilyKind,
) -> Option<SurfaceCurvePatchLayout> {
use cadmpeg_ir::geometry::SurfaceCurveFamilyKind;
let names: &[&[u8]] = match family {
SurfaceCurveFamilyKind::Blend => &[b"blend_int_cur", b"bldcur"],
SurfaceCurveFamilyKind::SurfaceConstrained => &[b"surf_int_cur", b"surfcur"],
SurfaceCurveFamilyKind::Parametric => &[b"par_int_cur", b"parcur"],
SurfaceCurveFamilyKind::Skin => &[b"skin_int_cur", b"d5c2_cur"],
};
let (marker, name) = find_owned_subtype_marker(bytes, names, int_width)?;
let mut position = marker + name.len() + 3;
decode_embedded_surface(bytes, &mut position, int_width)?;
decode_embedded_surface(bytes, &mut position, int_width)?;
position = decode_pcurve_block_with_end(bytes, position, int_width)?.1;
position = decode_pcurve_block_with_end(bytes, position, int_width)?.1;
let parameter_range = [
take_double_payload(bytes, &mut position)?,
take_double_payload(bytes, &mut position)?,
];
let discontinuities = [
take_float_array_payloads(bytes, &mut position, int_width)?,
take_float_array_payloads(bytes, &mut position, int_width)?,
take_float_array_payloads(bytes, &mut position, int_width)?,
];
Some(SurfaceCurvePatchLayout {
parameter_range,
discontinuities,
})
}
fn embedded_three_surface_intersection(toks: &[Token]) -> Option<EmbeddedThreeSurfaceIntersection> {
let (marker, _) = crate::nurbs::toks::find_owned_subtype_marker(toks, &["sss_int_cur"])?;
let mut cur = Cur::at(toks, marker + 2);
let RequiredSupportPair {
surfaces: [first, second],
pcurves: [first_pcurve, second_pcurve],
} = required_support_pair(&mut cur)?;
let parameter_range = [cur.take_range_value()?, cur.take_range_value()?];
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
let selector = cur.take_long()?;
let third = embedded_support(&mut cur)?;
let (mut third_pcurve, _) = pcurve_block_with_end(toks, cur.pos())?;
third.chart.normalize(&mut third_pcurve)?;
Some(EmbeddedThreeSurfaceIntersection {
surfaces: [first, second, third.surface],
pcurves: [first_pcurve, second_pcurve, third_pcurve],
parameter_range,
discontinuities,
selector,
})
}
pub struct ThreeSurfacePatchLayout {
pub parameter_range: [usize; 2],
pub discontinuities: [Vec<usize>; 3],
pub selector: usize,
}
pub fn three_surface_patch_layout(
bytes: &[u8],
int_width: RefWidth,
) -> Option<ThreeSurfacePatchLayout> {
let (marker, name_len) = find_owned_intcurve_subtype(bytes, b"sss_int_cur", int_width)?;
let mut position = marker + name_len + 3;
decode_embedded_surface(bytes, &mut position, int_width)?;
decode_embedded_surface(bytes, &mut position, int_width)?;
position = decode_pcurve_block_with_end(bytes, position, int_width)?.1;
position = decode_pcurve_block_with_end(bytes, position, int_width)?.1;
let parameter_range = [
take_double_payload(bytes, &mut position)?,
take_double_payload(bytes, &mut position)?,
];
let discontinuities = [
take_float_array_payloads(bytes, &mut position, int_width)?,
take_float_array_payloads(bytes, &mut position, int_width)?,
take_float_array_payloads(bytes, &mut position, int_width)?,
];
let selector = position;
take_tagged_int(bytes, &mut position, 0x04, int_width)?;
decode_embedded_surface(bytes, &mut position, int_width)?;
decode_pcurve_block_with_end(bytes, position, int_width)?;
Some(ThreeSurfacePatchLayout {
parameter_range,
discontinuities,
selector,
})
}
fn embedded_projection(toks: &[Token]) -> Option<EmbeddedProjection> {
let marker = crate::nurbs::toks::find_owned_intcurve_subtype(toks, "proj_int_cur")?;
let mut cur = Cur::at(toks, marker + 2);
let RequiredSupportPair { surfaces, pcurves } = required_support_pair(&mut cur)?;
let parameter_range = [cur.take_range_value()?, cur.take_range_value()?];
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
let discontinuity_flag = cur.take_bool()?;
let (source, source_end) = curve_block(toks, cur.pos())?;
cur.set_pos(source_end);
let flag = cur.take_bool()?;
let tail = if matches!(cur.peek(), Some(Token::SubtypeClose)) {
cadmpeg_ir::geometry::ProjectionTail::EarlyClose { flag }
} else {
cadmpeg_ir::geometry::ProjectionTail::Ranged {
flag,
parameter_range: [cur.take_range_value()?, cur.take_range_value()?],
role: cadmpeg_ir::geometry::ProjectionRole::parse(cur.take_str()?)?,
}
};
Some(EmbeddedProjection {
surfaces,
pcurves,
parameter_range,
discontinuities,
discontinuity_flag,
source,
tail,
})
}
pub struct ProjectionRoleSlot(usize);
impl ProjectionRoleSlot {
fn parse(bytes: &[u8], position: usize) -> Option<Self> {
let encoded = bytes.get(position..position.checked_add(7)?)?;
matches!(encoded, b"\x07\x05surf1" | b"\x07\x05surf2").then_some(Self(position + 2))
}
pub fn range(&self) -> std::ops::Range<usize> {
self.0..self.0 + 5
}
pub fn write(
&self,
bytes: &mut [u8],
role: cadmpeg_ir::geometry::ProjectionRole,
) -> Result<(), cadmpeg_core::CodecError> {
let payload: &[u8; 5] = match role {
cadmpeg_ir::geometry::ProjectionRole::Surf1 => b"surf1",
cadmpeg_ir::geometry::ProjectionRole::Surf2 => b"surf2",
};
let target = bytes.get_mut(self.range()).ok_or_else(|| {
cadmpeg_core::CodecError::Malformed("projection role payload is truncated".into())
})?;
target.copy_from_slice(payload);
Ok(())
}
}
pub enum ProjectionTailPatchLayout {
EarlyClose {
flag: usize,
},
Ranged {
flag: usize,
parameter_range: [usize; 2],
role: ProjectionRoleSlot,
},
}
pub struct ProjectionPatchLayout {
pub parameter_range: [usize; 2],
pub discontinuities: [Vec<usize>; 3],
pub discontinuity_flag: usize,
pub tail: ProjectionTailPatchLayout,
}
pub fn projection_patch_layout(bytes: &[u8], int_width: RefWidth) -> Option<ProjectionPatchLayout> {
let (marker, name_len) = find_owned_intcurve_subtype(bytes, b"proj_int_cur", int_width)?;
let mut position = marker + name_len + 3;
decode_embedded_surface(bytes, &mut position, int_width)?;
decode_embedded_surface(bytes, &mut position, int_width)?;
position = decode_pcurve_block_with_end(bytes, position, int_width)?.1;
position = decode_pcurve_block_with_end(bytes, position, int_width)?.1;
let parameter_range = [
take_double_payload(bytes, &mut position)?,
take_double_payload(bytes, &mut position)?,
];
let discontinuities = [
take_float_array_payloads(bytes, &mut position, int_width)?,
take_float_array_payloads(bytes, &mut position, int_width)?,
take_float_array_payloads(bytes, &mut position, int_width)?,
];
let discontinuity_flag = position;
take_bool(bytes, &mut position)?;
position = decode_curve_block(bytes, position, int_width)?.end();
let tail_flag = position;
take_bool(bytes, &mut position)?;
let tail = if bytes.get(position) == Some(&0x10) {
ProjectionTailPatchLayout::EarlyClose { flag: tail_flag }
} else {
let parameter_range = [
take_double_payload(bytes, &mut position)?,
take_double_payload(bytes, &mut position)?,
];
let role = ProjectionRoleSlot::parse(bytes, position)?;
ProjectionTailPatchLayout::Ranged {
flag: tail_flag,
parameter_range,
role,
}
};
Some(ProjectionPatchLayout {
parameter_range,
discontinuities,
discontinuity_flag,
tail,
})
}
fn embedded_intersection(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
toks: &[Token],
solved: &NurbsCurve,
table: &SubtypeTable,
) -> Option<Result<(EmbeddedIntersection, bool), cadmpeg_core::CodecError>> {
let names = ["int_int_cur", "surf_surf_int_cur", "surfintcur"];
let (marker, _) = crate::nurbs::toks::find_owned_subtype_marker(toks, &names)?;
let position = marker + 2;
context_first_intersection(toks, position)
.map(Ok)
.or_else(|| cache_first_intersection(ctx, toks, position, solved, table))
}
fn context_first_intersection(
toks: &[Token],
position: usize,
) -> Option<(EmbeddedIntersection, bool)> {
let mut cur = Cur::at(toks, position);
let RequiredSupportPair { surfaces, pcurves } = required_support_pair(&mut cur)?;
let parameter_range = [cur.take_range_value()?, cur.take_range_value()?];
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
let discontinuity_flag = cur.take_bool()?;
Some((
EmbeddedIntersection {
surfaces: surfaces.map(SupportSlot::Surface),
pcurves: pcurves.map(Some),
parameter_range,
discontinuities,
},
discontinuity_flag,
))
}
fn cache_first_intersection(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
toks: &[Token],
position: usize,
solved: &NurbsCurve,
table: &SubtypeTable,
) -> Option<Result<(EmbeddedIntersection, bool), cadmpeg_core::CodecError>> {
let mut cur = Cur::at(toks, position);
(cur.take_long()? > 0).then_some(())?;
(cur.take_enum()? == 0).then_some(())?;
let (_, cache_end) = curve_block(toks, cur.pos())?;
cur.set_pos(cache_end);
cur.take_f64()?;
let first = match optional_embedded_surface_with_bounds(ctx, &mut cur, table)? {
Ok(surface) => surface,
Err(error) => return Some(Err(error)),
};
let second = match optional_embedded_surface_with_bounds(ctx, &mut cur, table)? {
Ok(surface) => surface,
Err(error) => return Some(Err(error)),
};
let mut pcurves = [
nullable_embedded_pcurve(&mut cur)?.value(),
nullable_embedded_pcurve(&mut cur)?.value(),
];
for (pcurve, chart) in pcurves.iter_mut().zip([first.chart, second.chart]) {
if let Some(pcurve) = pcurve {
chart.normalize(pcurve)?;
}
}
let surfaces = [first.surface, second.surface];
let domain = nurbs_curve_parameter_domain(solved)?;
let parameter_range = [
cur.take_optional_range_value()?
.value()
.unwrap_or(domain[0]),
cur.take_optional_range_value()?
.value()
.unwrap_or(domain[1]),
];
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
let discontinuity_flag = cur.take_long()? != 0;
Some(Ok((
EmbeddedIntersection {
surfaces,
pcurves,
parameter_range,
discontinuities,
},
discontinuity_flag,
)))
}
pub struct IntersectionPatchLayout {
pub parameter_range: [usize; 2],
pub discontinuities: [Vec<usize>; 3],
pub discontinuity_flag: usize,
}
pub fn intersection_patch_layout(
bytes: &[u8],
int_width: RefWidth,
) -> Option<IntersectionPatchLayout> {
let names: [&[u8]; 3] = [b"int_int_cur", b"surf_surf_int_cur", b"surfintcur"];
let (marker, name) = find_owned_subtype_marker(bytes, &names, int_width)?;
let mut position = marker + name.len() + 3;
decode_embedded_surface(bytes, &mut position, int_width)?;
decode_embedded_surface(bytes, &mut position, int_width)?;
position = decode_pcurve_block_with_end(bytes, position, int_width)?.1;
position = decode_pcurve_block_with_end(bytes, position, int_width)?.1;
let parameter_range = [
take_double_payload(bytes, &mut position)?,
take_double_payload(bytes, &mut position)?,
];
let discontinuities = [
take_float_array_payloads(bytes, &mut position, int_width)?,
take_float_array_payloads(bytes, &mut position, int_width)?,
take_float_array_payloads(bytes, &mut position, int_width)?,
];
let discontinuity_flag = position;
take_bool(bytes, &mut position)?;
Some(IntersectionPatchLayout {
parameter_range,
discontinuities,
discontinuity_flag,
})
}
fn embedded_two_sided_offset(toks: &[Token]) -> Option<EmbeddedTwoSidedOffset> {
let marker = crate::nurbs::toks::find_owned_intcurve_subtype(toks, "off_int_cur")?;
let mut cur = Cur::at(toks, marker + 2);
let supports = [
optional_embedded_surface(&mut cur)?.value(),
optional_embedded_surface(&mut cur)?.value(),
];
let first_pcurve = optional_pcurve(&mut cur)?.value();
let second_pcurve = optional_pcurve(&mut cur)?.value();
let mut pcurves = [first_pcurve, second_pcurve];
for (pcurve, support) in pcurves.iter_mut().zip(&supports) {
if let (Some(pcurve), Some(support)) = (pcurve, support) {
support.chart.normalize(pcurve)?;
}
}
let surfaces = supports.map(|support| support.map(|support| support.surface));
let parameter_range = [cur.take_range_value()?, cur.take_range_value()?];
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
let discontinuity_flag = cur.take_bool()?;
let offsets = [
cur.take_range_value()? * LEN_TO_MM,
cur.take_range_value()? * LEN_TO_MM,
];
Some(EmbeddedTwoSidedOffset {
surfaces,
pcurves,
parameter_range,
discontinuities,
discontinuity_flag,
offsets,
})
}
fn optional_embedded_surface(cur: &mut Cur<'_>) -> Option<Nullable<EmbeddedSupport>> {
let start = cur.pos();
if cur.take_ident()? == "null_surface" {
return Some(Nullable::Null);
}
cur.set_pos(start);
embedded_support(cur).map(Nullable::Value)
}
fn optional_pcurve(cur: &mut Cur<'_>) -> Option<Nullable<PcurveNurbs>> {
let start = cur.pos();
if cur.take_ident()? == "nullbs" {
return Some(Nullable::Null);
}
let (pcurve, end) = pcurve_block_with_end(cur.toks(), start)?;
cur.set_pos(end);
Some(Nullable::Value(pcurve))
}
pub struct TwoSidedOffsetPatchLayout {
pub parameter_range: [usize; 2],
pub discontinuities: [Vec<usize>; 3],
pub discontinuity_flag: usize,
pub offsets: [usize; 2],
}
pub fn two_sided_offset_patch_layout(
bytes: &[u8],
int_width: RefWidth,
) -> Option<TwoSidedOffsetPatchLayout> {
let name = b"off_int_cur";
let (marker, name_len) = find_owned_intcurve_subtype(bytes, name, int_width)?;
let mut position = marker + name_len + 3;
skip_offset_support_surface(bytes, &mut position, int_width)?;
skip_offset_support_surface(bytes, &mut position, int_width)?;
skip_offset_support_pcurve(bytes, &mut position, int_width)?;
skip_offset_support_pcurve(bytes, &mut position, int_width)?;
let parameter_range = [
take_double_payload(bytes, &mut position)?,
take_double_payload(bytes, &mut position)?,
];
let discontinuities = [
take_float_array_payloads(bytes, &mut position, int_width)?,
take_float_array_payloads(bytes, &mut position, int_width)?,
take_float_array_payloads(bytes, &mut position, int_width)?,
];
let discontinuity_flag = position;
take_bool(bytes, &mut position)?;
let offsets = [
take_double_payload(bytes, &mut position)?,
take_double_payload(bytes, &mut position)?,
];
Some(TwoSidedOffsetPatchLayout {
parameter_range,
discontinuities,
discontinuity_flag,
offsets,
})
}
fn skip_offset_support_surface(
bytes: &[u8],
position: &mut usize,
int_width: RefWidth,
) -> Option<()> {
let start = *position;
if take_native_ident(bytes, position)?.as_str() == "null_surface" {
return Some(());
}
*position = start;
decode_embedded_surface(bytes, position, int_width)?;
Some(())
}
fn skip_offset_support_pcurve(
bytes: &[u8],
position: &mut usize,
int_width: RefWidth,
) -> Option<()> {
let start = *position;
if take_native_ident(bytes, position)?.as_str() == "nullbs" {
return Some(());
}
*position = decode_pcurve_block_with_end(bytes, start, int_width)?.1;
Some(())
}
fn decode_embedded_surface(
bytes: &[u8],
position: &mut usize,
int_width: RefWidth,
) -> Option<SurfaceGeometry> {
decode_embedded_surface_fields(bytes, position, int_width, false).map(|support| support.surface)
}
pub(super) fn embedded_surface(cur: &mut Cur<'_>) -> Option<SurfaceGeometry> {
embedded_surface_fields(cur, false).map(|value| value.surface)
}
pub(super) struct EmbeddedSupport {
pub(super) surface: SurfaceGeometry,
pub(super) chart: SupportChart,
pub(super) ranges: [[Option<f64>; 2]; 2],
}
pub(super) fn embedded_support(cur: &mut Cur<'_>) -> Option<EmbeddedSupport> {
embedded_surface_fields(cur, false)
}
pub(super) fn embedded_surface_with_ranges(cur: &mut Cur<'_>) -> Option<EmbeddedSupport> {
embedded_surface_fields(cur, true)
}
fn admitted_placement(
point: Point3,
axis: UnitVector3,
reference: UnitVector3,
) -> Option<(FinitePoint3, OrthonormalFrame3)> {
let frame = OrthonormalFrame3::from_units(axis, reference)?;
let point = FinitePoint3::new(point)?;
Some((point, frame))
}
fn skip_cone_bound(cur: &mut Cur<'_>) -> Option<()> {
if cur.take_bool()? {
cur.take_f64()?;
}
Some(())
}
pub(crate) fn analytic_support(
kind: &str,
cur: &mut Cur<'_>,
) -> Option<(SurfaceGeometry, SupportChart)> {
let point = cur.take_position()?;
let point = Point3::new(
point[0] * LEN_TO_MM,
point[1] * LEN_TO_MM,
point[2] * LEN_TO_MM,
);
match kind {
"plane" => {
let normal = normalized(cur.take_vector3()?)?;
let u_axis = normalized(cur.take_vector3()?)?;
let chart = SupportChart::plane(cur.take_bool()?);
let (origin, frame) = admitted_placement(point, normal, u_axis)?;
Some((
SurfaceGeometry::Solved(SolvedSurfaceGeometry::Plane(PlaneSurface::new(
origin, frame,
))),
chart,
))
}
"cone" => {
let native_axis = normalized(cur.take_vector3()?)?;
let major = cur.take_vector3()?;
let radius = major[0].hypot(major[1]).hypot(major[2]) * LEN_TO_MM;
let ref_direction = normalized(major)?;
let ratio = cur.take_f64()?;
skip_cone_bound(cur)?;
skip_cone_bound(cur)?;
let sine = cur.take_f64()?;
let cosine = cur.take_f64()?;
let u_scale = cur.take_f64()?;
let chart = SupportChart::cone(sine, cosine, ratio, u_scale, cur.take_bool()?)?;
let surface = if cone_is_circular_cylinder(sine, ratio) {
let (origin, frame) = admitted_placement(point, native_axis, ref_direction)?;
SurfaceGeometry::Solved(SolvedSurfaceGeometry::Cylinder(CylinderSurface::new(
origin,
frame,
PositiveLength::new(radius)?,
)))
} else {
let axis = if cone_axis_reversed(sine, cosine, ratio) {
native_axis.negated()
} else {
native_axis
};
let (origin, frame) = admitted_placement(point, axis, ref_direction)?;
SurfaceGeometry::Solved(SolvedSurfaceGeometry::Cone(ConeSurface::new(
origin,
frame,
NonNegativeLength::new(radius)?,
PositiveReal::new(ratio)?,
Angle::new(sine.abs().atan2(cosine.abs()))?,
)))
};
Some((surface, chart))
}
"sphere" => {
let radius = cur.take_f64()? * LEN_TO_MM;
let ref_direction = normalized(cur.take_vector3()?)?;
let axis = normalized(cur.take_vector3()?)?;
let chart = SupportChart::sphere(cur.take_bool()?);
let (center, frame) = admitted_placement(point, axis, ref_direction)?;
Some((
SurfaceGeometry::Solved(SolvedSurfaceGeometry::Sphere(SphereSurface::new(
center,
frame,
NonZeroLength::new(radius)?,
))),
chart,
))
}
"torus" => {
let axis = normalized(cur.take_vector3()?)?;
let major_radius = cur.take_f64()? * LEN_TO_MM;
let minor_radius = cur.take_f64()? * LEN_TO_MM;
let ref_direction = normalized(cur.take_vector3()?)?;
let chart = SupportChart::torus(major_radius, cur.take_bool()?);
let (center, frame) = admitted_placement(point, axis, ref_direction)?;
Some((
SurfaceGeometry::Solved(SolvedSurfaceGeometry::Torus(TorusSurface::new(
center,
frame,
PositiveLength::new(major_radius.abs())?,
NonZeroLength::new(minor_radius)?,
))),
chart,
))
}
_ => None,
}
}
fn embedded_surface_fields(cur: &mut Cur<'_>, preserve_ranges: bool) -> Option<EmbeddedSupport> {
let no_ranges = [[None, None], [None, None]];
let kind = cur.take_ident()?;
if kind == "spline" {
let (decoded, end) = surface_block(cur.toks(), cur.pos())?;
cur.set_pos(end);
let ranges = if preserve_ranges {
surface_ranges(cur)?
} else {
no_ranges
};
return Some(EmbeddedSupport {
surface: SurfaceGeometry::Solved(SolvedSurfaceGeometry::Nurbs(decoded)),
chart: SupportChart::Canonical,
ranges,
});
}
let (surface, chart) = analytic_support(kind, cur)?;
let ranges = if preserve_ranges {
surface_ranges(cur)?
} else {
if kind != "plane" {
for _ in 0..4 {
cur.take_bool()?;
}
}
no_ranges
};
Some(EmbeddedSupport {
surface,
chart,
ranges,
})
}
pub(super) fn decode_embedded_surface_with_ranges(
bytes: &[u8],
position: &mut usize,
int_width: RefWidth,
) -> Option<(SurfaceGeometry, [[Option<f64>; 2]; 2])> {
decode_embedded_surface_fields(bytes, position, int_width, true)
.map(|support| (support.surface, support.ranges))
}
fn decode_embedded_surface_fields(
bytes: &[u8],
position: &mut usize,
int_width: RefWidth,
preserve_ranges: bool,
) -> Option<EmbeddedSupport> {
let (tokens, offsets) = crate::sab::lex_payload(bytes, *position, int_width);
let mut cur = Cur::at(&tokens, 0);
let support = embedded_surface_fields(&mut cur, preserve_ranges)?;
*position = *offsets.get(cur.pos())?;
Some(support)
}
pub(super) struct EmbeddedSurfaceWithBounds {
pub(super) surface: SupportSlot,
pub(super) chart: SupportChart,
pub(super) bounds: [Option<f64>; 4],
}
pub(super) fn optional_embedded_surface_with_bounds(
ctx: &cadmpeg_core::decode::DecodeContext<'_>,
cur: &mut Cur<'_>,
table: &SubtypeTable,
) -> Option<Result<EmbeddedSurfaceWithBounds, cadmpeg_core::CodecError>> {
let toks = cur.toks();
let saved = cur.pos();
let kind = cur.take_ident();
if kind == Some("null_surface") {
return Some(Ok(EmbeddedSurfaceWithBounds {
surface: SupportSlot::Absent,
chart: SupportChart::Canonical,
bounds: [None; 4],
}));
}
if kind == Some("spline") {
if matches!(cur.peek(), Some(Token::True | Token::False)) {
cur.take_bool()?;
}
if let Some(scope) = crate::nurbs::toks::subtype_span(toks, cur.pos()) {
let reference = match scope.interior() {
[Token::Ident(name), Token::Long(index), ..] if name == "ref" => {
Some(usize::try_from(*index).ok()?)
}
_ => None,
};
let target = match reference {
Some(index) => table.span(index),
None => Some(scope),
};
let surface = if let Some(target) = target {
if let Some(surface) = owned_surface_cache_resolving_refs(ctx, target, table) {
SupportSlot::Surface(SurfaceGeometry::Solved(SolvedSurfaceGeometry::Nurbs(
propagate_resource!(surface),
)))
} else if propagate_resource!(
crate::nurbs::proc_surface::procedural_surface_resolving_refs(
ctx,
target.tokens(),
table,
)
.transpose()
)
.is_some()
{
SupportSlot::DeclaredOnly
} else if reference.is_some() {
SupportSlot::Absent
} else {
return None;
}
} else {
SupportSlot::Absent
};
cur.set_pos(cur.pos() + scope.tokens().len());
let mut bounds = [None; 4];
for bound in &mut bounds {
*bound = cur.take_optional_range_value()?.value();
}
return Some(Ok(EmbeddedSurfaceWithBounds {
surface,
chart: SupportChart::Canonical,
bounds,
}));
}
}
cur.set_pos(saved);
let EmbeddedSupport { surface, chart, .. } = embedded_support(cur)?;
let mut bounds = [None; 4];
if kind == Some("plane") || kind == Some("spline") {
for bound in &mut bounds {
*bound = cur.take_optional_range_value()?.value();
}
}
Some(Ok(EmbeddedSurfaceWithBounds {
surface: SupportSlot::Surface(surface),
chart,
bounds,
}))
}
fn compound_definition(toks: &[Token]) -> Option<CompoundDefinition> {
let (marker, _) = crate::nurbs::toks::find_owned_subtype_marker(toks, &["comp_int_cur"])?;
let mut cur = Cur::at(toks, marker + 2);
let parameters = cur.take_float_array()?;
let count = usize::try_from(cur.take_long()?).ok()?;
if count == 0 {
return None;
}
let mut component_parameters = Vec::new();
for _ in 0..count {
component_parameters.push(cur.take_f64()?);
}
if !matches!(cur.peek(), Some(Token::True | Token::False)) {
return None;
}
cur.bump();
let mut components = Vec::new();
for parameter in component_parameters {
let (curve, end) = curve_block(toks, cur.pos())?;
components.push(cadmpeg_ir::geometry::CompoundComponent {
parameter,
component: curve,
});
cur.set_pos(end);
}
Some(CompoundDefinition {
parameters,
components,
})
}
fn subset_definition(toks: &[Token]) -> Option<SubsetDefinition> {
let marker = crate::nurbs::toks::find_owned_intcurve_subtype(toks, "subset_int_cur")?;
let mut cur = Cur::at(toks, marker + 2);
let (source, source_end) = curve_block(toks, cur.pos())?;
cur.set_pos(source_end);
let range = [cur.take_range_value()?, cur.take_range_value()?];
Some((source, range))
}
fn vector_offset_definition(toks: &[Token]) -> Option<VectorOffsetDefinition> {
let marker = crate::nurbs::toks::find_owned_intcurve_subtype(toks, "offset_int_cur")?;
let mut cur = Cur::at(toks, marker + 2);
cur.take_bool()?;
let (source, source_end) = curve_block(toks, cur.pos())?;
cur.set_pos(source_end);
if !matches!(toks.get(cur.pos()), Some(Token::Double(_)))
|| !matches!(toks.get(cur.pos() + 1), Some(Token::Double(_)))
{
return None;
}
let parameter_range = [cur.take_f64()?, cur.take_f64()?];
let offset = cur.take_vector3()?;
let first_label = cur.take_str()?;
let first_code = cur.take_long()?;
let second_label = cur.take_str()?;
let second_code = cur.take_long()?;
if first_label != "source" || second_label != "offset" {
return None;
}
Some((
source,
parameter_range,
Vector3::new(
offset[0] * LEN_TO_MM,
offset[1] * LEN_TO_MM,
offset[2] * LEN_TO_MM,
),
cadmpeg_ir::geometry::VectorOffsetRoles {
source: first_code,
offset: second_code,
},
))
}
pub(super) fn helix_definition(toks: &[Token]) -> Option<HelixDefinition> {
let (marker, _) = crate::nurbs::toks::find_owned_subtype_marker(toks, &["helix_int_cur"])?;
let mut cur = Cur::at(toks, marker + 2);
let current_layout = optional_helix_revision(&mut cur)?;
let lower = cur.take_range_value()?;
let upper = cur.take_range_value()?;
let center = cur.take_position()?;
let take_frame_vector = |cur: &mut Cur<'_>| {
if current_layout {
cur.take_vector3()
} else {
cur.take_position()
}
};
let major = take_frame_vector(&mut cur)?;
let minor = take_frame_vector(&mut cur)?;
let pitch = take_frame_vector(&mut cur)?;
let apex_factor = cur.take_f64()?;
let axis = cur.take_vector3()?;
Some(HelixDefinition {
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_optional_helix_revision(
bytes: &[u8],
position: &mut usize,
int_width: RefWidth,
) -> Option<bool> {
if bytes.get(*position) != Some(&0x04) {
return Some(false);
}
let revision = take_tagged_int(bytes, position, 0x04, int_width)?;
(20_000..=99_999).contains(&revision).then_some(true)
}
pub(super) fn optional_helix_revision(cur: &mut Cur<'_>) -> Option<bool> {
if !matches!(cur.peek(), Some(Token::Long(_))) {
return Some(false);
}
let revision = cur.take_long()?;
(20_000..=99_999).contains(&revision).then_some(true)
}
pub fn record_trailing_surface_bounds(toks: &[Token]) -> Option<[Option<f64>; 4]> {
let mut position = 0usize;
while toks.get(position).is_some_and(Token::is_payload_ident) {
position += 1;
}
if !matches!(toks.get(position), Some(Token::Ref(_))) {
return None;
}
position += 1;
if !matches!(toks.get(position), Some(Token::Long(_))) {
return None;
}
position += 1;
if !matches!(toks.get(position), Some(Token::Ref(_))) {
return None;
}
position += 1;
if !matches!(toks.get(position), Some(Token::True | Token::False)) {
return None;
}
position += 1;
if !matches!(toks.get(position), Some(Token::SubtypeOpen)) {
return None;
}
let scope = crate::nurbs::toks::subtype_span(toks, position)?.tokens();
position += scope.len();
if !matches!(toks.get(position), Some(Token::True | Token::False)) {
return None;
}
let mut cur = Cur::at(toks, position);
let mut bounds = [None; 4];
for bound in &mut bounds {
*bound = cur.take_optional_range_value()?.value();
}
Some(bounds)
}
pub(crate) fn nurbs_curve_parameter_domain(curve: &NurbsCurve) -> Option<[f64; 2]> {
let degree = usize::try_from(curve.degree()).ok()?;
Some([
*curve.knots().get(degree)?,
*curve.knots().get(curve.pole_rows().count())?,
])
}
#[cfg(test)]
mod cache_form_tests {
use super::{cache_first_curve_context, nurbs_curve_parameter_domain, ProjectionRoleSlot};
use crate::kernel_header::RefWidth;
use crate::nurbs::toks::Cur;
use crate::sab::Token;
use cadmpeg_ir::geometry::nurbs::NurbsCurve;
use cadmpeg_ir::math::Point3;
#[test]
fn cache_first_support_decodes_the_solved_nurbs_support() {
use crate::nurbs::toks::SubtypeTable;
use cadmpeg_core::decode::{DecodeArena, DecodeContext, DecodePolicy};
const AXIS_POLES: u32 = 16;
let mut support = vec![
Token::Ident("spline".into()),
Token::Ident("nubs".into()),
Token::Long(1),
Token::Long(1),
Token::Enum(0),
Token::Enum(0),
Token::Enum(0),
Token::Enum(0),
Token::Long(i64::from(AXIS_POLES)),
Token::Long(i64::from(AXIS_POLES)),
];
for _ in 0..2 {
for knot in 0..AXIS_POLES {
support.extend([Token::Double(f64::from(knot)), Token::Long(1)]);
}
}
for v in 0..AXIS_POLES {
for u in 0..AXIS_POLES {
support.extend([
Token::Double(f64::from(u)),
Token::Double(f64::from(v)),
Token::Double(0.0),
]);
}
}
support.extend([
Token::False,
Token::False,
Token::False,
Token::False,
Token::Ident("null_surface".into()),
Token::Ident("nullbs".into()),
Token::Ident("nullbs".into()),
Token::False,
Token::False,
Token::Long(0),
Token::Long(0),
Token::Long(0),
Token::Long(7),
]);
for intersection in [false, true] {
let mut tokens = if intersection {
vec![
Token::Long(23_100),
Token::Enum(0),
Token::Ident("nubs".into()),
Token::Long(1),
Token::Enum(0),
Token::Long(2),
Token::Double(0.0),
Token::Long(1),
Token::Double(1.0),
Token::Long(1),
Token::Double(0.0),
Token::Double(0.0),
Token::Double(0.0),
Token::Double(1.0),
Token::Double(0.0),
Token::Double(0.0),
Token::Double(0.0),
]
} else {
vec![
Token::Long(23_100),
Token::Enum(2),
Token::False,
Token::False,
Token::Enum(0),
]
};
tokens.extend(support.clone());
let arena = DecodeArena::new();
let (ctx, _) =
DecodeContext::from_root_bytes(&[], &arena, &DecodePolicy::service()).unwrap();
let table = SubtypeTable::from_records(&[]);
let surfaces = if intersection {
super::cache_first_intersection(&ctx, &tokens, 0, &solved_curve(), &table)
.unwrap()
.unwrap()
.0
.surfaces
} else {
let mut cur = Cur::at(&tokens, 0);
let context = cache_first_curve_context(&ctx, &mut cur, &table)
.unwrap()
.unwrap();
assert_eq!(cur.pos(), tokens.len());
context.surfaces
};
let [super::SupportSlot::Surface(surface), super::SupportSlot::Absent] = surfaces
else {
panic!("one solved support and one absent support");
};
let Some(cadmpeg_ir::geometry::SolvedSurfaceGeometry::Nurbs(surface)) =
surface.solved()
else {
panic!("solved NURBS support");
};
assert_eq!(surface.u_count(), usize::try_from(AXIS_POLES).unwrap());
assert_eq!(surface.v_count(), usize::try_from(AXIS_POLES).unwrap());
assert_eq!(surface.poles()[0].get(), Point3::new(0.0, 0.0, 0.0));
assert_eq!(surface.poles()[255].get(), Point3::new(150.0, 150.0, 0.0));
ctx.finish_session().unwrap();
}
}
#[test]
fn projection_role_slot_rejects_a_truncated_payload() {
let original = b"\x07\x05surf1";
let slot = ProjectionRoleSlot::parse(original, 0).unwrap();
let mut bytes = original[..6].to_vec();
let before = bytes.clone();
let error = slot
.write(&mut bytes, cadmpeg_ir::geometry::ProjectionRole::Surf2)
.unwrap_err();
assert!(matches!(error, cadmpeg_core::CodecError::Malformed(_)));
assert_eq!(bytes, before);
}
fn push_int(bytes: &mut Vec<u8>, tag: u8, value: i64, int_width: RefWidth) {
bytes.push(tag);
match int_width {
RefWidth::Eight => bytes.extend_from_slice(&value.to_le_bytes()),
RefWidth::Four => bytes.extend_from_slice(
&(i32::try_from(value).expect("test integer fits")).to_le_bytes(),
),
}
}
fn push_ident(bytes: &mut Vec<u8>, value: &str) {
bytes.push(0x0d);
bytes.push(u8::try_from(value.len()).expect("short identifier"));
bytes.extend_from_slice(value.as_bytes());
}
fn push_cache_first_remainder(bytes: &mut Vec<u8>, int_width: RefWidth) {
push_ident(bytes, "null_surface");
push_ident(bytes, "null_surface");
push_ident(bytes, "nullbs");
push_ident(bytes, "nullbs");
bytes.extend_from_slice(&[0x0b, 0x0b]);
for _ in 0..3 {
push_int(bytes, 0x04, 0, int_width);
}
push_int(bytes, 0x04, 7, int_width);
}
fn solved_curve() -> NurbsCurve {
NurbsCurve::from_lanes(
1,
vec![0.0, 0.0, 1.0, 1.0],
vec![Point3::new(0.0, 0.0, 0.0), Point3::new(1.0, 0.0, 0.0)],
None,
false,
)
.unwrap()
}
#[test]
fn parameterized_cache_form_reads_the_interval_and_closed_form_enum() {
let asm_decode_arena = cadmpeg_core::decode::DecodeArena::new();
let (asm_decode_ctx, _) = cadmpeg_core::decode::DecodeContext::from_root_bytes(
&[],
&asm_decode_arena,
&cadmpeg_core::decode::DecodePolicy::default(),
)
.expect("test decode context");
for int_width in [RefWidth::Four, RefWidth::Eight] {
let mut bytes = Vec::new();
push_int(&mut bytes, 0x04, 23_100, int_width);
push_int(&mut bytes, 0x15, 2, int_width);
bytes.push(0x0a);
bytes.push(0x06);
bytes.extend_from_slice(&0.125f64.to_le_bytes());
bytes.push(0x0b);
push_int(&mut bytes, 0x15, 1, int_width);
push_cache_first_remainder(&mut bytes, int_width);
let solved = solved_curve();
let toks = crate::nurbs::toks::lex_test_span(&bytes, int_width)
.expect("valid single-record byte fixture");
let table = crate::nurbs::toks::test_table(&bytes, int_width)
.expect("valid single-record byte fixture");
let mut cur = Cur::at(&toks, 0);
let context = cache_first_curve_context(&asm_decode_ctx, &mut cur, &table)
.transpose()
.expect("decode the cache-first curve context")
.unwrap_or_else(|| {
panic!("parameterized cache-first context at width {int_width}")
});
assert_eq!(cur.pos(), toks.len());
assert_eq!(context.form.revision.get(), 23_100);
assert!(context.form.cache.parameterization().is_some());
let (context, form) =
context.into_intersection(nurbs_curve_parameter_domain(&solved).unwrap());
let parameterization = match form.cache {
cadmpeg_ir::geometry::RevisionCacheForm::Parameterization(value) => value,
cadmpeg_ir::geometry::RevisionCacheForm::SolvedCache { .. } => {
panic!("parameterized cache-first context")
}
};
assert_eq!(parameterization.interval, [Some(0.125), None]);
assert_eq!(parameterization.closed_form, 1);
assert_eq!(form.extension, 7);
assert_eq!(form.solved_range, [None, None]);
assert_eq!(context.parameter_range, [0.0, 1.0]);
}
}
#[test]
fn undefined_cache_form_is_rejected_for_verbatim_retention() {
let asm_decode_arena = cadmpeg_core::decode::DecodeArena::new();
let (asm_decode_ctx, _) = cadmpeg_core::decode::DecodeContext::from_root_bytes(
&[],
&asm_decode_arena,
&cadmpeg_core::decode::DecodePolicy::default(),
)
.expect("test decode context");
for int_width in [RefWidth::Four, RefWidth::Eight] {
let mut bytes = Vec::new();
push_int(&mut bytes, 0x04, 23_100, int_width);
push_int(&mut bytes, 0x15, 1, int_width);
push_cache_first_remainder(&mut bytes, int_width);
let toks = crate::nurbs::toks::lex_test_span(&bytes, int_width)
.expect("valid single-record byte fixture");
let table = crate::nurbs::toks::test_table(&bytes, int_width)
.expect("valid single-record byte fixture");
let mut cur = Cur::at(&toks, 0);
assert!(cache_first_curve_context(&asm_decode_ctx, &mut cur, &table)
.transpose()
.expect("decode the cache-first curve context")
.is_none());
}
}
}