use crate::nurbs::blend::{
compact_rb_blend_spl_sur, cyl_spl_sur, full_rb_blend_spl_sur, rolling_ball_side,
var_blend_spl_sur, vertex_blend_spl_sur,
};
use crate::nurbs::core::{curve_block, surface_block};
use crate::nurbs::pcurve::{decode_pcurve_block_with_end, pcurve_block_with_end, NurbsPcurve};
use crate::nurbs::proc_curve::{
embedded_base_curve_resolving_refs, embedded_surface, embedded_surface_with_ranges,
optional_embedded_surface_with_bounds, optional_helix_revision,
};
use crate::nurbs::reader::{normalized, take_native_ident, LEN_TO_MM};
use crate::nurbs::toks::{self, Cur, SubtypeTable};
use crate::sab::Token;
use cadmpeg_core::decode::bounded_len;
use cadmpeg_ir::geometry::{
BlendCrossSection, BlendRadiusLaw, CurveGeometry, NurbsCurve, NurbsSurface, SurfaceGeometry,
};
use cadmpeg_ir::math::{Point3, Vector3};
pub struct DecodedProceduralSurface {
pub definition: DecodedProceduralSurfaceDefinition,
pub cache_fit_tolerance: Option<f64>,
}
pub enum DecodedProceduralSurfaceDefinition {
Exact {
parameters: cadmpeg_ir::geometry::SplineSurfaceParameters,
extension: i64,
revision_form: Option<cadmpeg_ir::geometry::RevisionSurfaceForm>,
},
Compound {
parameters: Vec<f64>,
components: Vec<SurfaceGeometry>,
},
SubSurface {
support: SurfaceGeometry,
parameter_ranges: [[f64; 2]; 2],
},
Taper {
support: SurfaceGeometry,
reference: NurbsCurve,
pcurve: Option<NurbsPcurve>,
parameter: f64,
taper: cadmpeg_ir::geometry::TaperSurfaceKind,
revision_form: Option<cadmpeg_ir::geometry::RevisionSurfaceForm>,
},
Loft(EmbeddedLoft),
CompoundLoft(Box<EmbeddedCompoundLoft>),
RevisionCompoundLoft(Box<EmbeddedRevisionCompoundLoft>),
ScaledCompoundLoft(Box<EmbeddedScaledCompoundLoft>),
Skin(Box<EmbeddedSkinSurface>),
Law(Box<EmbeddedLawSurface>),
Net(Box<EmbeddedNetSurface>),
Sweep(Box<EmbeddedSweepSurface>),
TSpline(Box<cadmpeg_ir::geometry::TSplineSurfaceConstruction>),
Helix(Box<cadmpeg_ir::geometry::HelixSurfaceConstruction>),
Deformable(Box<EmbeddedDeformableSurface>),
G2Blend(Box<EmbeddedG2Blend>),
RevisionG2Blend(Box<EmbeddedRevisionG2Blend>),
Ruled {
first: NurbsCurve,
second: NurbsCurve,
},
Sum {
first: CurveGeometry,
second: CurveGeometry,
basepoint: Vector3,
revision_form: Option<cadmpeg_ir::geometry::RevisionSurfaceForm>,
},
Revolution {
directrix: CurveGeometry,
axis_origin: Point3,
axis_direction: Vector3,
angular_interval: [f64; 2],
parameter_interval: [f64; 2],
revision_form: Option<cadmpeg_ir::geometry::RevisionSurfaceForm>,
},
Offset {
support: SurfaceGeometry,
distance: f64,
u_sense: Option<i64>,
v_sense: Option<i64>,
extension_flags: Vec<bool>,
revision_form: Option<cadmpeg_ir::geometry::RevisionSurfaceForm>,
},
Extrusion {
directrix: NurbsCurve,
parameter_interval: [f64; 2],
direction: Vector3,
native_position: Point3,
revision_form: Option<cadmpeg_ir::geometry::RevisionSurfaceForm>,
},
Blend {
supports: Box<[Option<SurfaceGeometry>; 2]>,
spine: Option<NurbsCurve>,
radius: BlendRadiusLaw,
cross_section: BlendCrossSection,
native: Option<Box<EmbeddedRollingBall>>,
},
VariableBlend(Box<EmbeddedVariableBlend>),
VertexBlend(Box<EmbeddedVertexBlend>),
}
pub struct EmbeddedRollingBallSide {
pub support_kind: cadmpeg_ir::geometry::VariableBlendSupportKind,
pub surface: Option<SurfaceGeometry>,
pub surface_ranges: [[Option<f64>; 2]; 2],
pub curve: Option<CurveGeometry>,
pub curve_range: [Option<f64>; 2],
pub pcurve: Option<NurbsPcurve>,
pub location: Point3,
pub secondary_pcurve: Option<NurbsPcurve>,
pub extension: Option<i64>,
pub tertiary_pcurve: Option<NurbsPcurve>,
}
pub struct EmbeddedRevisionG2Blend {
pub revision: i64,
pub leading_parameters: [f64; 2],
pub sides: Box<[EmbeddedRollingBallSide; 2]>,
pub center: CurveGeometry,
pub center_range: [Option<f64>; 2],
pub radii: [f64; 2],
pub radius_selector: i64,
pub u_range: [Option<f64>; 2],
pub v_range: [Option<f64>; 2],
pub shape_prefix: i64,
pub shape_parameter: f64,
pub shape_length: f64,
pub shape_tail: i64,
pub tail_enum: i64,
pub tail_parameterization: Option<cadmpeg_ir::geometry::RevisionSurfaceParameterization>,
pub discontinuities: [Vec<f64>; 6],
pub tail_flag: bool,
pub tail_extensions: [i64; 3],
}
pub struct EmbeddedRollingBallThirdSide {
pub label: String,
pub surface: SurfaceGeometry,
pub curve: NurbsCurve,
pub pcurve: Option<NurbsPcurve>,
pub direction: Vector3,
pub secondary_pcurve: Option<NurbsPcurve>,
pub extension: i64,
pub tertiary_pcurve: Option<NurbsPcurve>,
pub flag: bool,
}
pub struct EmbeddedVariableBlend {
pub subtype: cadmpeg_ir::geometry::VariableBlendSurfaceSubtype,
pub revision: i64,
pub sides: Box<[EmbeddedRollingBallSide; 2]>,
pub slice: CurveGeometry,
pub slice_range: [Option<f64>; 2],
pub offsets: [f64; 2],
pub radius_kind: cadmpeg_ir::geometry::VariableBlendRadiusKind,
pub first_value: cadmpeg_ir::geometry::VariableBlendValue,
pub second_value: Option<cadmpeg_ir::geometry::VariableBlendValue>,
pub cross_section: Option<cadmpeg_ir::geometry::VariableBlendCrossSection>,
pub u_range: [Option<f64>; 2],
pub v_range: [Option<f64>; 2],
pub shape_prefix: i64,
pub shape_parameter: f64,
pub shape_length: f64,
pub shape_tail: i64,
pub tail_enum: i64,
pub tail_parameterization: Option<cadmpeg_ir::geometry::RevisionSurfaceParameterization>,
pub discontinuities: [Vec<f64>; 6],
pub tail_flag: bool,
pub tail_extensions: [i64; 3],
pub secondary_curve: Option<CurveGeometry>,
pub secondary_range: [Option<f64>; 2],
pub convexity: cadmpeg_ir::geometry::VariableBlendConvexity,
pub render_mode: cadmpeg_ir::geometry::VariableBlendRenderMode,
pub post_range: [Option<f64>; 2],
pub post_curve: Option<NurbsCurve>,
pub post_pcurve: Option<NurbsPcurve>,
}
pub enum EmbeddedVertexBlendBoundaryGeometry {
Circle {
curve: CurveGeometry,
curve_endpoints: [Option<f64>; 2],
form: i64,
twists: Vec<Point3>,
parameters: [f64; 2],
sense: bool,
},
Degenerate {
location: Point3,
normals: [Vector3; 2],
},
Pcurve {
surface: SurfaceGeometry,
support_bounds: [Option<f64>; 4],
pcurve: Option<NurbsPcurve>,
sense: bool,
fit_tolerance: f64,
},
Plane {
normal: Vector3,
parameters: [f64; 2],
curve: CurveGeometry,
curve_endpoints: [Option<f64>; 2],
},
}
pub struct EmbeddedVertexBlendBoundary {
pub boundary_type: bool,
pub magic: Vector3,
pub u_smoothing: bool,
pub v_smoothing: bool,
pub fullness: f64,
pub geometry: EmbeddedVertexBlendBoundaryGeometry,
}
pub struct EmbeddedVertexBlend {
pub revision: Option<i64>,
pub boundaries: Vec<EmbeddedVertexBlendBoundary>,
pub grid_size: i64,
pub fit_tolerance: f64,
}
pub enum EmbeddedRollingBallRadiusSelector {
None,
Value(f64),
}
pub struct EmbeddedRollingBall {
pub definition_index: i64,
pub sides: Box<[EmbeddedRollingBallSide; 2]>,
pub slice: CurveGeometry,
pub slice_range: [Option<f64>; 2],
pub offsets: [f64; 2],
pub radius_selector: EmbeddedRollingBallRadiusSelector,
pub u_range: [Option<f64>; 2],
pub v_range: [Option<f64>; 2],
pub shape_prefix: i64,
pub parameters: [f64; 2],
pub tail: i64,
pub tail_enum: i64,
pub tail_parameterization: Option<cadmpeg_ir::geometry::RevisionSurfaceParameterization>,
pub discontinuities: [Vec<f64>; 6],
pub tail_flag: bool,
pub third: Option<Box<EmbeddedRollingBallThirdSide>>,
pub tail_extensions: [i64; 3],
}
pub struct EmbeddedG2Side {
pub label: String,
pub surface: SurfaceGeometry,
pub curve: NurbsCurve,
pub pcurves: [Option<NurbsPcurve>; 2],
pub direction: Vector3,
}
pub enum EmbeddedG2FirstShape {
Full {
surface: Option<NurbsSurface>,
tolerance: Option<f64>,
},
None {
coefficients: [f64; 9],
tolerance: f64,
extension: Option<cadmpeg_ir::geometry::LoftBridgeToken>,
pcurve: Option<NurbsPcurve>,
},
}
pub struct EmbeddedG2Blend {
pub first: EmbeddedG2Side,
pub singularity: i64,
pub first_shape: EmbeddedG2FirstShape,
pub second: EmbeddedG2Side,
pub second_exact_surface: NurbsSurface,
pub center_curve: NurbsCurve,
pub center_parameters: [f64; 2],
pub center_flag: i64,
pub parameter_ranges: [[f64; 2]; 2],
pub trailing_parameters: [f64; 4],
pub discontinuities: [Vec<f64>; 3],
}
#[allow(clippy::option_option)] pub(crate) fn decode_nullable_embedded_pcurve(
bytes: &[u8],
position: &mut usize,
int_width: usize,
) -> Option<Option<NurbsPcurve>> {
let saved = *position;
if take_native_ident(bytes, position).as_deref() == Some("nullbs") {
return Some(None);
}
*position = saved;
let (pcurve, end) = decode_pcurve_block_with_end(bytes, *position, int_width)?;
*position = end;
Some(Some(pcurve))
}
#[allow(clippy::option_option)] pub(crate) fn nullable_embedded_pcurve(cur: &mut Cur<'_>) -> Option<Option<NurbsPcurve>> {
let saved = cur.pos();
if cur.take_ident() == Some("nullbs") {
return Some(None);
}
cur.set_pos(saved);
let (pcurve, end) = pcurve_block_with_end(cur.toks(), cur.pos())?;
cur.set_pos(end);
Some(Some(pcurve))
}
fn g2_side(cur: &mut Cur<'_>) -> Option<EmbeddedG2Side> {
let label = cur.take_str()?.to_string();
let surface = embedded_surface(cur)?;
let (curve, curve_end) = curve_block(cur.toks(), cur.pos())?;
cur.set_pos(curve_end);
let first = nullable_embedded_pcurve(cur)?;
let direction = cur.take_vector3()?;
let second = nullable_embedded_pcurve(cur)?;
Some(EmbeddedG2Side {
label,
surface,
curve,
pcurves: [first, second],
direction: Vector3::new(direction[0], direction[1], direction[2]),
})
}
fn bridge_token(cur: &mut Cur<'_>) -> Option<cadmpeg_ir::geometry::LoftBridgeToken> {
use cadmpeg_ir::geometry::LoftBridgeToken;
match cur.peek()? {
Token::True | Token::False => Some(LoftBridgeToken::Boolean(cur.take_bool()?)),
Token::Long(_) => Some(LoftBridgeToken::Integer(cur.take_long()?)),
Token::Double(_) => Some(LoftBridgeToken::Double(cur.take_f64()?)),
Token::Enum(_) => Some(LoftBridgeToken::Enum(cur.take_enum()?)),
Token::Str(_) => Some(LoftBridgeToken::Text(cur.take_str()?.to_string())),
_ => None,
}
}
#[allow(
clippy::option_option,
reason = "outer None rejects malformed trailing fields; inner None is a valid absent tolerance"
)]
fn optional_trailing_cache_tolerance(cur: &mut Cur<'_>) -> Option<Option<f64>> {
if cur.at_scope_end() {
Some(None)
} else {
let tolerance = cur.take_f64()? * LEN_TO_MM;
cur.at_scope_end().then_some(Some(tolerance))
}
}
fn g2_blend_spl_sur(
toks: &[Token],
resolver: Option<&SubtypeTable>,
) -> Option<DecodedProceduralSurface> {
let names = ["g2_blend_spl_sur", "g2blnsur"];
let (start, name) = toks::find_owned_subtype_marker(toks, &names)?;
let span = toks::subtype_span(toks, start)?;
let mut cur = Cur::at(span, 2);
if matches!(cur.peek(), Some(Token::Long(_))) {
(name == "g2_blend_spl_sur").then_some(())?;
let revision = cur.take_long()?;
(revision > 0).then_some(())?;
let leading_parameters = [cur.take_f64()?, cur.take_f64()?];
let sides = Box::new([
rolling_ball_side(&mut cur, resolver)?,
rolling_ball_side(&mut cur, resolver)?,
]);
let table = resolver?;
let center = embedded_base_curve_resolving_refs(&mut cur, table)?;
let center_range = [
cur.take_optional_range_value()?,
cur.take_optional_range_value()?,
];
let radii = [cur.take_f64()? * LEN_TO_MM, cur.take_f64()? * LEN_TO_MM];
let radius_selector = cur.take_enum()?;
let u_range = [
cur.take_optional_range_value()?,
cur.take_optional_range_value()?,
];
let v_range = [
cur.take_optional_range_value()?,
cur.take_optional_range_value()?,
];
let shape_prefix = cur.take_long()?;
let shape_parameter = cur.take_f64()?;
let shape_length = cur.take_f64()? * LEN_TO_MM;
let shape_tail = cur.take_long()?;
let RevisionSurfaceTail {
enumeration: tail_enum,
fit_tolerance,
solved_cache_domains: _,
parameterization,
discontinuities,
tail_flag,
} = revision_surface_tail(&mut cur)?;
let tail_extensions = [cur.take_long()?, cur.take_long()?, cur.take_long()?];
cur.at_scope_end().then_some(())?;
return Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::RevisionG2Blend(Box::new(
EmbeddedRevisionG2Blend {
revision,
leading_parameters,
sides,
center: CurveGeometry::Nurbs(center),
center_range,
radii,
radius_selector,
u_range,
v_range,
shape_prefix,
shape_parameter,
shape_length,
shape_tail,
tail_enum,
tail_parameterization: parameterization,
discontinuities,
tail_flag,
tail_extensions,
},
)),
cache_fit_tolerance: fit_tolerance,
});
}
let first = g2_side(&mut cur)?;
let singularity = cur.take_enum()?;
let first_shape = if cur.peek().is_some_and(Token::is_payload_ident) {
let saved = cur.pos();
if cur.take_ident() == Some("nullbs") {
EmbeddedG2FirstShape::Full {
surface: None,
tolerance: None,
}
} else {
cur.set_pos(saved);
let (surface, surface_end) = surface_block(span, cur.pos())?;
cur.set_pos(surface_end);
EmbeddedG2FirstShape::Full {
surface: Some(surface),
tolerance: Some(cur.take_f64()? * LEN_TO_MM),
}
}
} else {
let mut coefficients = [0.0; 9];
for coefficient in &mut coefficients {
*coefficient = cur.take_f64()?;
}
let tolerance = cur.take_f64()? * LEN_TO_MM;
let extension = (!matches!(cur.peek(), Some(token)
if matches!(token, Token::Str(_)) || token.is_payload_ident()))
.then(|| bridge_token(&mut cur))
.flatten();
let pcurve = nullable_embedded_pcurve(&mut cur)?;
EmbeddedG2FirstShape::None {
coefficients,
tolerance,
extension,
pcurve,
}
};
let second = g2_side(&mut cur)?;
let (second_exact, second_exact_end) = surface_block(span, cur.pos())?;
cur.set_pos(second_exact_end);
let (center, center_end) = curve_block(span, cur.pos())?;
cur.set_pos(center_end);
let center_parameters = [cur.take_f64()?, cur.take_f64()?];
let center_flag = cur.take_long()?;
let parameter_ranges = [
[cur.take_f64()?, cur.take_f64()?],
[cur.take_f64()?, cur.take_f64()?],
];
let mut trailing_parameters = [0.0; 4];
for parameter in &mut trailing_parameters {
*parameter = cur.take_f64()?;
}
let (_, cache_end) = surface_block(span, cur.pos())?;
let cache_fit_tolerance = match span.get(cache_end) {
Some(Token::Double(value)) => Some(*value * LEN_TO_MM),
_ => None,
};
cur.set_pos(cache_end + usize::from(cache_fit_tolerance.is_some()));
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::G2Blend(Box::new(EmbeddedG2Blend {
first,
singularity,
first_shape,
second,
second_exact_surface: second_exact,
center_curve: center,
center_parameters,
center_flag,
parameter_ranges,
trailing_parameters,
discontinuities,
})),
cache_fit_tolerance,
})
}
pub struct EmbeddedLoftProfileData {
pub surface: Option<SurfaceGeometry>,
pub support_bounds: [Option<f64>; 4],
pub pcurve: Option<NurbsPcurve>,
pub secondary_pcurve: Option<NurbsPcurve>,
pub first_flag: Option<bool>,
pub asm_extension: Option<i64>,
pub subdata: cadmpeg_ir::geometry::LoftSubdata,
pub direction: Option<Vector3>,
}
pub struct EmbeddedLoftProfileMember {
pub type_code: i64,
pub curve: NurbsCurve,
pub endpoints: Option<[Option<f64>; 2]>,
pub data: EmbeddedLoftProfileData,
}
pub struct EmbeddedLoftPath {
pub curve: Option<NurbsCurve>,
pub endpoints: Option<[Option<f64>; 2]>,
pub auxiliaries: Vec<NurbsCurve>,
pub flag: i64,
}
pub struct EmbeddedRevisionCompoundLoft {
pub revision: i64,
pub tail_enum: i64,
pub tail_parameterization: Option<cadmpeg_ir::geometry::RevisionSurfaceParameterization>,
pub discontinuities: [Vec<f64>; 6],
pub tail_flag: bool,
pub base_profile: Vec<EmbeddedLoftProfileMember>,
pub base_path: EmbeddedLoftPath,
pub entries: Vec<EmbeddedLoftSectionEntry>,
pub flags: [bool; 2],
pub kind: i64,
pub kind_flags: [bool; 2],
pub selector: i64,
pub direction: Option<Vector3>,
pub direction_curve: Option<NurbsCurve>,
pub interval: [Option<f64>; 2],
pub trailing_curve: Option<NurbsCurve>,
}
pub struct EmbeddedLoftSectionEntry {
pub parameter: f64,
pub profile: Vec<EmbeddedLoftProfileMember>,
pub path: EmbeddedLoftPath,
}
pub struct EmbeddedLoft {
pub sections: [Vec<EmbeddedLoftSectionEntry>; 2],
pub revision_form: Option<cadmpeg_ir::geometry::LoftRevisionForm>,
pub parameters: cadmpeg_ir::geometry::SplineSurfaceParameters,
pub closures: [i64; 2],
pub singularities: [i64; 2],
pub mode: i64,
pub bridge: Vec<cadmpeg_ir::geometry::LoftBridgeToken>,
}
pub struct EmbeddedCompoundLoftScale {
pub members: Vec<EmbeddedLoftProfileMember>,
pub path: NurbsCurve,
pub auxiliaries: Vec<NurbsCurve>,
pub tail: [i64; 2],
}
pub enum EmbeddedCompoundLoftDirection {
Vector(Vector3),
Curve(NurbsCurve),
}
pub enum EmbeddedCompoundLoftTail {
Six {
flags: [bool; 2],
scale: Box<EmbeddedCompoundLoftScale>,
selector: i64,
direction: Vector3,
parameter_range: [f64; 2],
curve: NurbsCurve,
},
Seven {
first_flag: bool,
first_scale: Option<Box<EmbeddedCompoundLoftScale>>,
second_flag: bool,
second_scale: Box<EmbeddedCompoundLoftScale>,
selector: i64,
direction: Vector3,
trailing_flags: [bool; 2],
},
Zero {
flags: [bool; 2],
selector: i64,
direction: EmbeddedCompoundLoftDirection,
trailing_flags: [bool; 2],
},
}
pub struct EmbeddedCompoundLoft {
pub scales: Box<[Option<EmbeddedCompoundLoftScale>; 4]>,
pub fifth_scale: Option<Box<EmbeddedCompoundLoftScale>>,
pub flags: [bool; 2],
pub tail: EmbeddedCompoundLoftTail,
}
pub enum EmbeddedScaledCompoundLoftShape {
Full,
None {
parameter_ranges: [[f64; 2]; 2],
parameters: [Vec<f64>; 2],
},
}
pub enum EmbeddedScaledCompoundLoftBranch {
ExtendedVector {
first_scale: Option<Box<EmbeddedCompoundLoftScale>>,
second_scale: Box<EmbeddedCompoundLoftScale>,
selector: i64,
direction: Vector3,
},
ExtendedCurve {
scale: Option<Box<EmbeddedCompoundLoftScale>>,
flag: bool,
singularity: i64,
curve: NurbsCurve,
},
Direct {
flag: bool,
selector: i64,
direction: EmbeddedCompoundLoftDirection,
},
}
pub struct EmbeddedScaledCompoundLoft {
pub singularity: i64,
pub shape: EmbeddedScaledCompoundLoftShape,
pub discontinuities: [Vec<f64>; 6],
pub discontinuity_flag: bool,
pub scales: Box<[Option<EmbeddedCompoundLoftScale>; 3]>,
pub flags: [bool; 2],
pub selector: i64,
pub branch: EmbeddedScaledCompoundLoftBranch,
pub trailing_flags: [bool; 2],
pub tail_kind: i64,
pub tail_directions: [Vector3; 2],
pub tail_singularity: i64,
pub tail_curve: NurbsCurve,
}
pub enum EmbeddedLawExpression {
Null,
Text(String),
Integer(i64),
Double(f64),
Point(Point3),
Vector(Vector3),
Transform {
scalars: [f64; 13],
enums: [i64; 3],
},
TransformVec {
vectors: [Vector3; 4],
scale: f64,
flags: [bool; 3],
},
Edge {
curve: NurbsCurve,
endpoints: Option<[Option<f64>; 2]>,
parameters: [f64; 2],
},
Spline {
native_id: i64,
knots: Vec<f64>,
controls: Vec<f64>,
point: Point3,
},
Algebraic {
operator: String,
operands: Vec<EmbeddedLawExpression>,
},
}
pub struct EmbeddedLawFormula {
pub name: String,
pub variables: Vec<EmbeddedLawExpression>,
}
pub struct EmbeddedLawSurface {
pub parameter_ranges: Option<[[f64; 2]; 2]>,
pub primary: EmbeddedLawFormula,
pub additional: Vec<EmbeddedLawFormula>,
pub tail: cadmpeg_ir::geometry::LawSurfaceTail,
pub discontinuities: [Vec<f64>; 6],
}
pub enum EmbeddedSkinSurfaceLayout {
Profiles {
profiles: Vec<EmbeddedLoftProfileMember>,
path: NurbsCurve,
tail: [i64; 2],
},
Compact {
curve: NurbsCurve,
subdata: cadmpeg_ir::geometry::LoftSubdata,
first_tail: i64,
secondary_curve: NurbsCurve,
second_tail: i64,
},
}
pub struct EmbeddedSkinSurface {
pub surface_boolean: i64,
pub surface_normal: i64,
pub surface_direction: i64,
pub count: i64,
pub parameter: f64,
pub inner_count: i64,
pub layout: EmbeddedSkinSurfaceLayout,
pub direction: Vector3,
pub trailing_parameter: f64,
pub formula: EmbeddedLawFormula,
pub parameter_curve: NurbsCurve,
pub discontinuities: [Vec<f64>; 6],
pub discontinuity_flag: bool,
}
pub struct EmbeddedNetSurface {
pub sections: Box<[Vec<EmbeddedLoftSectionEntry>; 2]>,
pub frame_parameters: [f64; 12],
pub flag: i64,
pub directions: [Vector3; 4],
pub formulas: Box<[EmbeddedLawFormula; 4]>,
pub discontinuities: [Vec<f64>; 6],
pub discontinuity_flag: bool,
}
pub enum EmbeddedSweepSurfaceLayout {
ProfileFirst {
profile: NurbsCurve,
spine: NurbsCurve,
secondary_kind: i64,
directions: [Vector3; 5],
origin: Point3,
parameters: [f64; 4],
formulas: Box<[EmbeddedLawFormula; 3]>,
},
ExplicitFormula {
profile: NurbsCurve,
mode: i64,
profile_range: [f64; 2],
profile_frame: Option<(Point3, Vector3)>,
origin: Point3,
directions: [Vector3; 3],
trajectory_flag: bool,
path: NurbsCurve,
path_range: [f64; 2],
path_parameter: f64,
formula_flag: bool,
formula: EmbeddedLawFormula,
trailing_flag: bool,
},
ExplicitGuide {
profile: NurbsCurve,
mode: i64,
profile_range: [f64; 2],
profile_frame: Option<(Point3, Vector3)>,
origin: Point3,
directions: [Vector3; 3],
trajectory_flag: bool,
path: NurbsCurve,
path_range: [f64; 2],
path_parameter: f64,
guide_flags: [bool; 2],
guide_curve: NurbsCurve,
guide_range: [f64; 2],
guide_modes: [i64; 2],
guide_parameters: [f64; 6],
trailing_flags: [bool; 3],
},
ExplicitSurface {
profile: NurbsCurve,
mode: i64,
profile_range: [f64; 2],
profile_frame: Option<(Point3, Vector3)>,
origin: Point3,
directions: [Vector3; 3],
trajectory_flag: bool,
path: NurbsCurve,
path_range: [f64; 2],
path_parameter: f64,
singularity: i64,
support_surface: SurfaceGeometry,
auxiliary_curve: Option<NurbsCurve>,
support_flag: bool,
legacy_flag: Option<bool>,
},
LawDriven {
profile: NurbsCurve,
mode: i64,
profile_range: [f64; 2],
profile_frame: Option<(Point3, Vector3)>,
origin: Point3,
directions: [Vector3; 3],
first_law: EmbeddedLawExpression,
first_mode: i64,
first_range: [f64; 2],
law_direction: Vector3,
path_mode: i64,
path_flag: bool,
path: NurbsCurve,
path_range: [f64; 2],
path_parameter: f64,
second_law_flag: bool,
second_law: EmbeddedLawExpression,
formula_mode: i64,
formula: EmbeddedLawFormula,
trailing_flag: bool,
},
}
pub struct EmbeddedSweepSurface {
pub primary_kind: i64,
pub revision_form: Option<cadmpeg_ir::geometry::SweepRevisionForm>,
pub layout: EmbeddedSweepSurfaceLayout,
pub discontinuities: [Vec<f64>; 6],
pub discontinuity_flag: bool,
}
pub struct EmbeddedDeformableSurface {
pub support: SurfaceGeometry,
pub revision_form: Option<cadmpeg_ir::geometry::RevisionSurfaceForm>,
pub data: EmbeddedDeformableSurfaceData,
pub discontinuities: [Vec<f64>; 6],
pub discontinuity_flag: bool,
}
pub enum EmbeddedDeformableSurfaceData {
Resolved(cadmpeg_ir::geometry::DeformableSurfaceData),
SurfaceCurve {
surface: SurfaceGeometry,
native_id: i64,
flag: bool,
first_parameter: f64,
selector: i64,
second_parameter: f64,
curve: NurbsCurve,
vectors: [Vector3; 4],
frame_parameter: f64,
flags: [bool; 3],
parameter_triples: Vec<[f64; 3]>,
},
Full {
leading_vectors: [Vector3; 4],
leading_parameter: f64,
leading_flags: [bool; 3],
selector: i64,
surface: SurfaceGeometry,
native_id: i64,
flag: bool,
first_parameter: f64,
version_value: Option<i64>,
second_parameter: f64,
curve: NurbsCurve,
frames: Box<[cadmpeg_ir::geometry::DeformableVectorFrame; 2]>,
trailing_value: i64,
},
}
#[allow(clippy::option_option)] fn compound_loft_scale(cur: &mut Cur<'_>) -> Option<Option<EmbeddedCompoundLoftScale>> {
if matches!(cur.peek(), Some(Token::True | Token::False)) {
return Some(None);
}
let count = usize::try_from(cur.take_long()?).ok()?;
if count > 100_000 {
return None;
}
let mut members = Vec::with_capacity(count);
for _ in 0..count {
let type_code = cur.take_long()?;
let (curve, curve_end) = curve_block(cur.toks(), cur.pos())?;
cur.set_pos(curve_end);
let data = loft_profile_data(cur)?;
members.push(EmbeddedLoftProfileMember {
type_code,
curve,
endpoints: None,
data,
});
}
let (path, path_end) = curve_block(cur.toks(), cur.pos())?;
cur.set_pos(path_end);
let auxiliary_count = usize::try_from(cur.take_long()?).ok()?;
if auxiliary_count > 100_000 {
return None;
}
let mut auxiliaries = Vec::with_capacity(auxiliary_count);
for _ in 0..auxiliary_count {
let (curve, curve_end) = curve_block(cur.toks(), cur.pos())?;
cur.set_pos(curve_end);
auxiliaries.push(curve);
}
let tail = [cur.take_long()?, cur.take_long()?];
Some(Some(EmbeddedCompoundLoftScale {
members,
path,
auxiliaries,
tail,
}))
}
pub(crate) fn ellipse_to_nurbs(
center: [f64; 3],
normal: [f64; 3],
major: [f64; 3],
ratio: f64,
) -> Option<NurbsCurve> {
let length = (major[0] * major[0] + major[1] * major[1] + major[2] * major[2]).sqrt();
(length.is_finite() && length > 0.0).then_some(())?;
let minor_direction = [
normal[1] * major[2] - normal[2] * major[1],
normal[2] * major[0] - normal[0] * major[2],
normal[0] * major[1] - normal[1] * major[0],
];
let minor_length = (minor_direction[0] * minor_direction[0]
+ minor_direction[1] * minor_direction[1]
+ minor_direction[2] * minor_direction[2])
.sqrt();
(minor_length.is_finite() && minor_length > 0.0).then_some(())?;
let minor_scale = ratio * length / minor_length;
let minor = [
minor_direction[0] * minor_scale,
minor_direction[1] * minor_scale,
minor_direction[2] * minor_scale,
];
let at = |mj: f64, mn: f64| {
Point3::new(
(center[0] + mj * major[0] + mn * minor[0]) * LEN_TO_MM,
(center[1] + mj * major[1] + mn * minor[1]) * LEN_TO_MM,
(center[2] + mj * major[2] + mn * minor[2]) * LEN_TO_MM,
)
};
let w = std::f64::consts::FRAC_1_SQRT_2;
Some(NurbsCurve {
degree: 2,
knots: vec![
0.0, 0.0, 0.0, 0.25, 0.25, 0.5, 0.5, 0.75, 0.75, 1.0, 1.0, 1.0,
],
control_points: vec![
at(1.0, 0.0),
at(1.0, 1.0),
at(0.0, 1.0),
at(-1.0, 1.0),
at(-1.0, 0.0),
at(-1.0, -1.0),
at(0.0, -1.0),
at(1.0, -1.0),
at(1.0, 0.0),
],
weights: Some(vec![1.0, w, 1.0, w, 1.0, w, 1.0, w, 1.0]),
periodic: false,
})
}
#[derive(Clone, Copy)]
enum RevisionLoftMemberForm {
Support,
PcurvePair,
}
impl RevisionLoftMemberForm {
fn of(type_code: i64) -> Self {
if type_code == 0 {
Self::PcurvePair
} else {
Self::Support
}
}
}
const LOFT_ASM_EXTENSION_ABSENT_THROUGH: u32 = 22600;
fn revision_loft_carries_asm_extension(table: &SubtypeTable) -> bool {
table
.save_format_version()
.is_none_or(|version| version > LOFT_ASM_EXTENSION_ABSENT_THROUGH)
}
fn revision_loft_profile_data(
cur: &mut Cur<'_>,
table: &SubtypeTable,
form: RevisionLoftMemberForm,
asm_extension_present: bool,
) -> Option<EmbeddedLoftProfileData> {
let (surface, support_bounds, pcurve, secondary_pcurve, first_flag) = match form {
RevisionLoftMemberForm::Support => {
let (surface, support_bounds) = optional_embedded_surface_with_bounds(cur, table)?;
let pcurve = nullable_embedded_pcurve(cur)?;
let first_flag = cur.take_bool()?;
(surface, support_bounds, pcurve, None, Some(first_flag))
}
RevisionLoftMemberForm::PcurvePair => {
let pcurve = nullable_embedded_pcurve(cur)?;
let secondary_pcurve = nullable_embedded_pcurve(cur)?;
(None, [None; 4], pcurve, secondary_pcurve, None)
}
};
let asm_extension = if asm_extension_present {
Some(cur.take_long()?)
} else {
None
};
let subdata = loft_subdata_form(cur, true)?;
let direction = if cur.take_bool()? {
let value = cur.take_vector3()?;
Some(Vector3::new(value[0], value[1], value[2]))
} else {
None
};
Some(EmbeddedLoftProfileData {
surface,
support_bounds,
pcurve,
secondary_pcurve,
first_flag,
asm_extension,
subdata,
direction,
})
}
fn revision_loft_section(
cur: &mut Cur<'_>,
table: &SubtypeTable,
asm_extension_present: bool,
) -> Option<Vec<EmbeddedLoftSectionEntry>> {
let count = usize::try_from(cur.take_long()?).ok()?;
let count = bounded_len(count as u64, 1, cur.rest().len())?;
let mut entries = Vec::with_capacity(count);
for _ in 0..count {
let parameter = cur.take_f64()?;
let member_count = usize::try_from(cur.take_long()?).ok()?;
let member_count = bounded_len(member_count as u64, 1, cur.rest().len())?;
let mut profile = Vec::with_capacity(member_count);
for _ in 0..member_count {
let type_code = cur.take_long()?;
let curve = embedded_base_curve_resolving_refs(cur, table)?;
let endpoints = [
cur.take_optional_range_value()?,
cur.take_optional_range_value()?,
];
let data = revision_loft_profile_data(
cur,
table,
RevisionLoftMemberForm::of(type_code),
asm_extension_present,
)?;
profile.push(EmbeddedLoftProfileMember {
type_code,
curve,
endpoints: Some(endpoints),
data,
});
}
let saved = cur.pos();
let (path_curve, path_endpoints) = if cur.take_ident() == Some("null_curve") {
(None, None)
} else {
cur.set_pos(saved);
let curve = embedded_base_curve_resolving_refs(cur, table)?;
let endpoints = [
cur.take_optional_range_value()?,
cur.take_optional_range_value()?,
];
(Some(curve), Some(endpoints))
};
let auxiliary_count = usize::try_from(cur.take_long()?).ok()?;
let auxiliary_count = bounded_len(auxiliary_count as u64, 1, cur.rest().len())?;
let mut auxiliaries = Vec::with_capacity(auxiliary_count);
for _ in 0..auxiliary_count {
let (auxiliary, auxiliary_end) = curve_block(cur.toks(), cur.pos())?;
cur.set_pos(auxiliary_end);
auxiliaries.push(auxiliary);
}
let flag = cur.take_long()?;
entries.push(EmbeddedLoftSectionEntry {
parameter,
profile,
path: EmbeddedLoftPath {
curve: path_curve,
endpoints: path_endpoints,
auxiliaries,
flag,
},
});
}
Some(entries)
}
fn loft_subdata(cur: &mut Cur<'_>) -> Option<cadmpeg_ir::geometry::LoftSubdata> {
loft_subdata_form(cur, false)
}
fn loft_subdata_form(
cur: &mut Cur<'_>,
revision: bool,
) -> Option<cadmpeg_ir::geometry::LoftSubdata> {
use cadmpeg_ir::geometry::{LoftSubdata, LoftSubdataRow};
let type_code = cur.take_long()?;
let row_count = cur.take_long()?;
let column_count = cur.take_long()?;
let rows_to_read = if type_code == 211 {
1
} else {
usize::try_from(row_count).ok()?
};
let columns_to_read = usize::try_from(column_count).ok()?;
let rows_to_read = bounded_len(rows_to_read as u64, 2, cur.rest().len())?;
let mut rows = Vec::with_capacity(rows_to_read);
for _ in 0..rows_to_read {
let parameters = [cur.take_f64()?, cur.take_f64()?];
let mut columns = Vec::new();
if type_code != 211 {
columns.reserve(columns_to_read);
for _ in 0..columns_to_read {
columns.push([cur.take_f64()?, cur.take_f64()?]);
}
}
let extra = if revision && type_code != 211 {
Some([cur.take_f64()?, cur.take_f64()?])
} else {
None
};
rows.push(LoftSubdataRow {
parameters,
columns,
extra,
});
}
Some(LoftSubdata {
type_code,
row_count,
column_count,
rows,
})
}
fn loft_profile_data(cur: &mut Cur<'_>) -> Option<EmbeddedLoftProfileData> {
let surface = embedded_surface(cur)?;
let saved = cur.pos();
let pcurve = if cur.take_ident() == Some("nullbs") {
None
} else {
cur.set_pos(saved);
let (pcurve, end) = pcurve_block_with_end(cur.toks(), cur.pos())?;
cur.set_pos(end);
Some(pcurve)
};
let first_flag = cur.take_bool()?;
let asm_extension = cur.take_long()?;
let subdata = loft_subdata(cur)?;
let direction = if cur.take_bool()? {
let value = cur.take_vector3()?;
Some(Vector3::new(value[0], value[1], value[2]))
} else {
None
};
Some(EmbeddedLoftProfileData {
surface: Some(surface),
support_bounds: [None; 4],
pcurve,
secondary_pcurve: None,
first_flag: Some(first_flag),
asm_extension: Some(asm_extension),
subdata,
direction,
})
}
fn loft_section(cur: &mut Cur<'_>) -> Option<Vec<EmbeddedLoftSectionEntry>> {
let count = usize::try_from(cur.take_long()?).ok()?;
let count = bounded_len(count as u64, 1, cur.rest().len())?;
let mut entries = Vec::with_capacity(count);
for _ in 0..count {
let parameter = cur.take_f64()?;
let member_count = usize::try_from(cur.take_long()?).ok()?;
let member_count = bounded_len(member_count as u64, 1, cur.rest().len())?;
let mut profile = Vec::with_capacity(member_count);
for _ in 0..member_count {
let type_code = cur.take_long()?;
let (curve, curve_end) = curve_block(cur.toks(), cur.pos())?;
cur.set_pos(curve_end);
let data = loft_profile_data(cur)?;
profile.push(EmbeddedLoftProfileMember {
type_code,
curve,
endpoints: None,
data,
});
}
let (curve, curve_end) = curve_block(cur.toks(), cur.pos())?;
cur.set_pos(curve_end);
let auxiliary_count = usize::try_from(cur.take_long()?).ok()?;
let auxiliary_count = bounded_len(auxiliary_count as u64, 1, cur.rest().len())?;
let mut auxiliaries = Vec::with_capacity(auxiliary_count);
for _ in 0..auxiliary_count {
let (auxiliary, auxiliary_end) = curve_block(cur.toks(), cur.pos())?;
cur.set_pos(auxiliary_end);
auxiliaries.push(auxiliary);
}
let flag = cur.take_long()?;
entries.push(EmbeddedLoftSectionEntry {
parameter,
profile,
path: EmbeddedLoftPath {
curve: Some(curve),
endpoints: None,
auxiliaries,
flag,
},
});
}
Some(entries)
}
fn revision_loft(
span: &[Token],
position: usize,
resolver: Option<&SubtypeTable>,
) -> Option<DecodedProceduralSurface> {
let table = resolver?;
let mut cur = Cur::at(span, position);
let revision = cur.take_long()?;
(revision > 0).then_some(())?;
let asm_extension_present = revision_loft_carries_asm_extension(table);
let sections = [
revision_loft_section(&mut cur, table, asm_extension_present)?,
revision_loft_section(&mut cur, table, asm_extension_present)?,
];
let wrap_ranges = [
[
cur.take_optional_range_value()?,
cur.take_optional_range_value()?,
],
[
cur.take_optional_range_value()?,
cur.take_optional_range_value()?,
],
];
let mut flags = [false; 4];
for flag in &mut flags {
*flag = cur.take_bool()?;
}
let ints = [cur.take_long()?, cur.take_long()?];
let RevisionSurfaceTail {
enumeration: tail_enum,
fit_tolerance,
solved_cache_domains: _,
parameterization,
discontinuities,
tail_flag,
} = revision_surface_tail(&mut cur)?;
cur.at_scope_end().then_some(())?;
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Loft(EmbeddedLoft {
sections,
revision_form: Some(cadmpeg_ir::geometry::LoftRevisionForm {
revision,
flags,
ints,
tail_enum,
tail_parameterization: parameterization,
discontinuities,
tail_flag,
}),
parameters: cadmpeg_ir::geometry::SplineSurfaceParameters::RevisionRanges {
intervals: wrap_ranges,
},
closures: [0, 0],
singularities: [0, 0],
mode: 0,
bridge: Vec::new(),
}),
cache_fit_tolerance: fit_tolerance,
})
}
fn loft_spl_sur(
toks: &[Token],
resolver: Option<&SubtypeTable>,
) -> Option<DecodedProceduralSurface> {
use cadmpeg_ir::geometry::LoftBridgeToken;
let names = ["loft_spl_sur", "loftsur"];
let (start, name) = toks::find_owned_subtype_marker(toks, &names)?;
let span = toks::subtype_span(toks, start)?;
let mut cur = Cur::at(span, 2);
if matches!(cur.peek(), Some(Token::Long(_))) && name == "loft_spl_sur" {
if let Some(decoded) = revision_loft(span, cur.pos(), resolver) {
return Some(decoded);
}
}
let sections = [loft_section(&mut cur)?, loft_section(&mut cur)?];
let parameter_ranges = [
[cur.take_f64()?, cur.take_f64()?],
[cur.take_f64()?, cur.take_f64()?],
];
let closures = [cur.take_enum()?, cur.take_enum()?];
let singularities = [cur.take_enum()?, cur.take_enum()?];
let mode = cur.take_long()?;
let mut bridge = Vec::new();
while toks::marker_at(span, cur.pos()).is_none() {
match cur.peek()? {
Token::True | Token::False => {
bridge.push(LoftBridgeToken::Boolean(cur.take_bool()?));
}
Token::Long(_) => bridge.push(LoftBridgeToken::Integer(cur.take_long()?)),
Token::Double(_) => bridge.push(LoftBridgeToken::Double(cur.take_f64()?)),
Token::Enum(_) => bridge.push(LoftBridgeToken::Enum(cur.take_enum()?)),
Token::Str(_) => bridge.push(LoftBridgeToken::Text(cur.take_str()?.to_string())),
_ => return None,
}
}
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
let cache_fit_tolerance = optional_trailing_cache_tolerance(&mut cur)?;
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Loft(EmbeddedLoft {
sections,
revision_form: None,
parameters: cadmpeg_ir::geometry::SplineSurfaceParameters::OrderedRanges {
ranges: parameter_ranges,
},
closures,
singularities,
mode,
bridge,
}),
cache_fit_tolerance,
})
}
fn revision_cl_scale(
cur: &mut Cur<'_>,
table: &SubtypeTable,
asm_extension_present: bool,
) -> Option<(Vec<EmbeddedLoftProfileMember>, EmbeddedLoftPath)> {
let member_count = usize::try_from(cur.take_long()?).ok()?;
let member_count = bounded_len(member_count as u64, 1, cur.rest().len())?;
let mut profile = Vec::with_capacity(member_count);
for _ in 0..member_count {
let type_code = cur.take_long()?;
let curve = embedded_base_curve_resolving_refs(cur, table)?;
let endpoints = [
cur.take_optional_range_value()?,
cur.take_optional_range_value()?,
];
let data = revision_loft_profile_data(
cur,
table,
RevisionLoftMemberForm::of(type_code),
asm_extension_present,
)?;
profile.push(EmbeddedLoftProfileMember {
type_code,
curve,
endpoints: Some(endpoints),
data,
});
}
let saved = cur.pos();
let (path_curve, path_endpoints) = if cur.take_ident() == Some("null_curve") {
(None, None)
} else {
cur.set_pos(saved);
let curve = embedded_base_curve_resolving_refs(cur, table)?;
let endpoints = [
cur.take_optional_range_value()?,
cur.take_optional_range_value()?,
];
(Some(curve), Some(endpoints))
};
let auxiliary_count = usize::try_from(cur.take_long()?).ok()?;
let auxiliary_count = bounded_len(auxiliary_count as u64, 1, cur.rest().len())?;
let mut auxiliaries = Vec::with_capacity(auxiliary_count);
for _ in 0..auxiliary_count {
let (auxiliary, auxiliary_end) = curve_block(cur.toks(), cur.pos())?;
cur.set_pos(auxiliary_end);
auxiliaries.push(auxiliary);
}
let flag = cur.take_long()?;
Some((
profile,
EmbeddedLoftPath {
curve: path_curve,
endpoints: path_endpoints,
auxiliaries,
flag,
},
))
}
fn revision_compound_loft(
span: &[Token],
resolver: Option<&SubtypeTable>,
) -> Option<DecodedProceduralSurface> {
let table = resolver?;
let mut cur = Cur::at(span, 2);
let revision = cur.take_long()?;
(revision > 0).then_some(())?;
let RevisionSurfaceTail {
enumeration: tail_enum,
fit_tolerance,
solved_cache_domains: _,
parameterization,
discontinuities,
tail_flag,
} = revision_surface_tail(&mut cur)?;
let asm_extension_present = revision_loft_carries_asm_extension(table);
let (base_profile, base_path) = revision_cl_scale(&mut cur, table, asm_extension_present)?;
let entry_count = usize::try_from(cur.take_long()?).ok()?;
let entry_count = bounded_len(entry_count as u64, 1, cur.rest().len())?;
let mut entries = Vec::with_capacity(entry_count);
for _ in 0..entry_count {
let (profile, path) = revision_cl_scale(&mut cur, table, asm_extension_present)?;
let parameter = cur.take_f64()?;
entries.push(EmbeddedLoftSectionEntry {
parameter,
profile,
path,
});
}
let flags = [cur.take_bool()?, cur.take_bool()?];
let kind = cur.take_long()?;
(kind == 0).then_some(())?;
let kind_flags = [cur.take_bool()?, cur.take_bool()?];
let selector = cur.take_long()?;
let (direction, direction_curve) = if selector == 0 {
let value = cur.take_vector3()?;
(Some(Vector3::new(value[0], value[1], value[2])), None)
} else {
let (curve, curve_end) = curve_block(span, cur.pos())?;
cur.set_pos(curve_end);
(None, Some(curve))
};
let interval = [
cur.take_optional_range_value()?,
cur.take_optional_range_value()?,
];
let trailing_curve = if interval.iter().all(Option::is_some) {
let (curve, curve_end) = curve_block(span, cur.pos())?;
cur.set_pos(curve_end);
Some(curve)
} else {
None
};
cur.at_scope_end().then_some(())?;
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::RevisionCompoundLoft(Box::new(
EmbeddedRevisionCompoundLoft {
revision,
tail_enum,
tail_parameterization: parameterization,
discontinuities,
tail_flag,
base_profile,
base_path,
entries,
flags,
kind,
kind_flags,
selector,
direction,
direction_curve,
interval,
trailing_curve,
},
)),
cache_fit_tolerance: fit_tolerance,
})
}
fn compound_loft_spl_sur(
toks: &[Token],
resolver: Option<&SubtypeTable>,
) -> Option<DecodedProceduralSurface> {
let (start, _) = toks::find_owned_subtype_marker(toks, &["cl_loft_spl_sur"])?;
let span = toks::subtype_span(toks, start)?;
let mut cur = Cur::at(span, 2);
if matches!(cur.peek(), Some(Token::Long(_))) {
return revision_compound_loft(span, resolver);
}
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
let cache_fit_tolerance = Some(cur.take_f64()? * LEN_TO_MM);
let scales = Box::new([
compound_loft_scale(&mut cur)?,
compound_loft_scale(&mut cur)?,
compound_loft_scale(&mut cur)?,
compound_loft_scale(&mut cur)?,
]);
let fifth_scale = if matches!(cur.peek(), Some(Token::Long(_))) {
compound_loft_scale(&mut cur)?.map(Box::new)
} else {
None
};
let flags = [cur.take_bool()?, cur.take_bool()?];
let kind = cur.take_long()?;
let tail = match kind {
6 => {
let tail_flags = [cur.take_bool()?, cur.take_bool()?];
let scale = Box::new(compound_loft_scale(&mut cur)??);
let selector = cur.take_long()?;
let direction = cur.take_vector3()?;
let parameter_range = [cur.take_range_value()?, cur.take_range_value()?];
let (curve, _) = curve_block(span, cur.pos())?;
EmbeddedCompoundLoftTail::Six {
flags: tail_flags,
scale,
selector,
direction: Vector3::new(direction[0], direction[1], direction[2]),
parameter_range,
curve,
}
}
7 => {
let first_flag = cur.take_bool()?;
let first_scale = compound_loft_scale(&mut cur)?.map(Box::new);
let second_flag = cur.take_bool()?;
let second_scale = Box::new(compound_loft_scale(&mut cur)??);
let selector = cur.take_long()?;
let direction = cur.take_vector3()?;
let trailing_flags = [cur.take_bool()?, cur.take_bool()?];
EmbeddedCompoundLoftTail::Seven {
first_flag,
first_scale,
second_flag,
second_scale,
selector,
direction: Vector3::new(direction[0], direction[1], direction[2]),
trailing_flags,
}
}
0 => {
let tail_flags = [cur.take_bool()?, cur.take_bool()?];
let selector = cur.take_long()?;
let direction = if selector == 0 {
let value = cur.take_vector3()?;
EmbeddedCompoundLoftDirection::Vector(Vector3::new(value[0], value[1], value[2]))
} else {
let (curve, curve_end) = curve_block(span, cur.pos())?;
cur.set_pos(curve_end);
EmbeddedCompoundLoftDirection::Curve(curve)
};
let trailing_flags = [cur.take_bool()?, cur.take_bool()?];
EmbeddedCompoundLoftTail::Zero {
flags: tail_flags,
selector,
direction,
trailing_flags,
}
}
_ => return None,
};
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::CompoundLoft(Box::new(
EmbeddedCompoundLoft {
scales,
fifth_scale,
flags,
tail,
},
)),
cache_fit_tolerance,
})
}
fn scaled_compound_loft_spl_sur(toks: &[Token]) -> Option<DecodedProceduralSurface> {
let names = ["scaled_cloft_spl_sur", "sclclftsur"];
let (start, _) = toks::find_owned_subtype_marker(toks, &names)?;
let span = toks::subtype_span(toks, start)?;
let mut cur = Cur::at(span, 2);
let singularity = cur.take_enum()?;
let (shape, cache_fit_tolerance) = if cur.peek().is_some_and(Token::is_payload_ident) {
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
let tolerance = cur.take_f64()? * LEN_TO_MM;
(EmbeddedScaledCompoundLoftShape::Full, Some(tolerance))
} else {
let parameter_ranges = [
[cur.take_range_value()?, cur.take_range_value()?],
[cur.take_range_value()?, cur.take_range_value()?],
];
let parameters = [cur.take_float_array()?, cur.take_float_array()?];
(
EmbeddedScaledCompoundLoftShape::None {
parameter_ranges,
parameters,
},
None,
)
};
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
let discontinuity_flag = cur.take_bool()?;
let scales = Box::new([
compound_loft_scale(&mut cur)?,
compound_loft_scale(&mut cur)?,
compound_loft_scale(&mut cur)?,
]);
let flags = [cur.take_bool()?, cur.take_bool()?];
let selector = cur.take_long()?;
let extended = cur.take_bool()?;
let branch = if extended {
let first_scale = compound_loft_scale(&mut cur)?.map(Box::new);
if cur.take_bool()? {
let second_scale = Box::new(compound_loft_scale(&mut cur)??);
let selector = cur.take_long()?;
let direction = cur.take_vector3()?;
EmbeddedScaledCompoundLoftBranch::ExtendedVector {
first_scale,
second_scale,
selector,
direction: Vector3::new(direction[0], direction[1], direction[2]),
}
} else {
let flag = cur.take_bool()?;
let singularity = cur.take_enum()?;
let (curve, curve_end) = curve_block(span, cur.pos())?;
cur.set_pos(curve_end);
EmbeddedScaledCompoundLoftBranch::ExtendedCurve {
scale: first_scale,
flag,
singularity,
curve,
}
}
} else {
let flag = cur.take_bool()?;
let selector = cur.take_long()?;
let direction = if selector == 0 {
let direction = cur.take_vector3()?;
EmbeddedCompoundLoftDirection::Vector(Vector3::new(
direction[0],
direction[1],
direction[2],
))
} else {
let (curve, curve_end) = curve_block(span, cur.pos())?;
cur.set_pos(curve_end);
EmbeddedCompoundLoftDirection::Curve(curve)
};
EmbeddedScaledCompoundLoftBranch::Direct {
flag,
selector,
direction,
}
};
let trailing_flags = [cur.take_bool()?, cur.take_bool()?];
let tail_kind = cur.take_long()?;
let first = cur.take_vector3()?;
let second = cur.take_vector3()?;
let tail_singularity = cur.take_enum()?;
let (tail_curve, _) = curve_block(span, cur.pos())?;
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::ScaledCompoundLoft(Box::new(
EmbeddedScaledCompoundLoft {
singularity,
shape,
discontinuities,
discontinuity_flag,
scales,
flags,
selector,
branch,
trailing_flags,
tail_kind,
tail_directions: [
Vector3::new(first[0], first[1], first[2]),
Vector3::new(second[0], second[1], second[2]),
],
tail_singularity,
tail_curve,
},
)),
cache_fit_tolerance,
})
}
pub(crate) fn law_expression(cur: &mut Cur<'_>, depth: usize) -> Option<EmbeddedLawExpression> {
law_expression_resolving(cur, depth, None)
}
fn sweep_law_expression(cur: &mut Cur<'_>) -> Option<EmbeddedLawExpression> {
if matches!(cur.peek(), Some(Token::Str(_))) {
return Some(EmbeddedLawExpression::Text(cur.take_str()?.to_string()));
}
law_expression(cur, 0)
}
fn law_expression_resolving(
cur: &mut Cur<'_>,
depth: usize,
resolver: Option<&SubtypeTable>,
) -> Option<EmbeddedLawExpression> {
if depth > 64 {
return None;
}
match cur.peek()? {
Token::Long(_) => {
return Some(EmbeddedLawExpression::Integer(cur.take_long()?));
}
Token::Double(_) => return Some(EmbeddedLawExpression::Double(cur.take_f64()?)),
Token::Position(_) => {
let value = cur.take_position()?;
return Some(EmbeddedLawExpression::Point(Point3::new(
value[0] * LEN_TO_MM,
value[1] * LEN_TO_MM,
value[2] * LEN_TO_MM,
)));
}
Token::Vector3(_) => {
let value = cur.take_vector3()?;
return Some(EmbeddedLawExpression::Vector(Vector3::new(
value[0], value[1], value[2],
)));
}
_ => {}
}
let operator = cur.take_str()?.to_string();
match operator.as_str() {
"null_law" => Some(EmbeddedLawExpression::Null),
"TRANS" => {
if matches!(cur.peek(), Some(Token::Vector3(_))) {
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 scale = cur.take_f64()?;
let flags = [cur.take_bool()?, cur.take_bool()?, cur.take_bool()?];
return Some(EmbeddedLawExpression::TransformVec {
vectors,
scale,
flags,
});
}
let mut scalars = [0.0; 13];
for scalar in &mut scalars {
*scalar = cur.take_f64()?;
}
let enums = [cur.take_enum()?, cur.take_enum()?, cur.take_enum()?];
Some(EmbeddedLawExpression::Transform { scalars, enums })
}
"EDGE" => {
let (curve, endpoints) = if let Some((curve, end)) = curve_block(cur.toks(), cur.pos())
{
cur.set_pos(end);
let endpoints = matches!(cur.peek(), Some(Token::True | Token::False))
.then(|| {
Some([
cur.take_optional_range_value()?,
cur.take_optional_range_value()?,
])
})
.flatten();
(curve, endpoints)
} else {
let table = resolver?;
let curve = embedded_base_curve_resolving_refs(cur, table)?;
let endpoints = Some([
cur.take_optional_range_value()?,
cur.take_optional_range_value()?,
]);
(curve, endpoints)
};
let parameters = [cur.take_f64()?, cur.take_f64()?];
Some(EmbeddedLawExpression::Edge {
curve,
endpoints,
parameters,
})
}
"SPLINE_LAW" => {
let native_id = cur.take_long()?;
let knots = cur.take_float_array()?;
let controls = cur.take_float_array()?;
let point = cur.take_position()?;
Some(EmbeddedLawExpression::Spline {
native_id,
knots,
controls,
point: Point3::new(
point[0] * LEN_TO_MM,
point[1] * LEN_TO_MM,
point[2] * LEN_TO_MM,
),
})
}
_ => {
let arity = match operator.as_str() {
"COS" | "SIN" | "TAN" | "COT" | "SEC" | "CSC" | "COSH" | "SINH" | "TANH"
| "COTH" | "SECH" | "CSCH" | "ARCCOS" | "ARCSIN" | "ARCTAN" | "ARCOT"
| "ARCSEC" | "ARCCSC" | "ARCCOSH" | "ARCSINH" | "ARCTANH" | "ARCOTH"
| "ARCSECH" | "ARCCSCH" | "ABS" | "EXP" | "LN" | "LOG" | "SIGN" | "SIZE"
| "SET" | "SQRT" | "NORM" | "NOT" => 1,
"CROSS" | "DOT" | "DCUR" | "O" | "ROTATE" | "TERM" => 2,
"VEC" | "DSURF" => 3,
_ => return None,
};
let operands = (0..arity)
.map(|_| law_expression_resolving(cur, depth + 1, resolver))
.collect::<Option<Vec<_>>>()?;
Some(EmbeddedLawExpression::Algebraic { operator, operands })
}
}
}
pub(crate) fn law_formula(cur: &mut Cur<'_>) -> Option<EmbeddedLawFormula> {
law_formula_resolving(cur, None)
}
fn law_formula_resolving(
cur: &mut Cur<'_>,
resolver: Option<&SubtypeTable>,
) -> Option<EmbeddedLawFormula> {
let name = cur.take_str()?.to_string();
if name == "null_law" {
return Some(EmbeddedLawFormula {
name,
variables: Vec::new(),
});
}
let count = usize::try_from(cur.take_long()?).ok()?;
if count > 100_000 {
return None;
}
let variables = (0..count)
.map(|_| law_expression_resolving(cur, 0, resolver))
.collect::<Option<Vec<_>>>()?;
Some(EmbeddedLawFormula { name, variables })
}
fn skin_spl_sur(toks: &[Token]) -> Option<DecodedProceduralSurface> {
let names = ["skin_spl_sur", "skinsur"];
let (start, _) = toks::find_owned_subtype_marker(toks, &names)?;
let span = toks::subtype_span(toks, start)?;
let mut cur = Cur::at(span, 2);
let surface_boolean = cur.take_enum()?;
let surface_normal = cur.take_enum()?;
let surface_direction = cur.take_enum()?;
let count = cur.take_long()?;
let parameter = cur.take_f64()?;
let inner_count = cur.take_long()?;
let layout = if cur.peek().is_some_and(Token::is_payload_ident) {
let (curve, curve_end) = curve_block(span, cur.pos())?;
cur.set_pos(curve_end);
let subdata = loft_subdata(&mut cur)?;
let first_tail = cur.take_long()?;
let (secondary_curve, secondary_end) = curve_block(span, cur.pos())?;
cur.set_pos(secondary_end);
let second_tail = cur.take_long()?;
EmbeddedSkinSurfaceLayout::Compact {
curve,
subdata,
first_tail,
secondary_curve,
second_tail,
}
} else {
let profile_count = usize::try_from(inner_count).ok()?;
if profile_count > 100_000 {
return None;
}
let mut profiles = Vec::with_capacity(profile_count);
for _ in 0..profile_count {
let type_code = cur.take_long()?;
let (curve, curve_end) = curve_block(span, cur.pos())?;
cur.set_pos(curve_end);
let data = loft_profile_data(&mut cur)?;
profiles.push(EmbeddedLoftProfileMember {
type_code,
curve,
endpoints: None,
data,
});
}
let (path, path_end) = curve_block(span, cur.pos())?;
cur.set_pos(path_end);
let tail = [cur.take_long()?, cur.take_long()?];
EmbeddedSkinSurfaceLayout::Profiles {
profiles,
path,
tail,
}
};
let direction = cur.take_vector3()?;
let trailing_parameter = cur.take_f64()?;
let formula = law_formula(&mut cur)?;
let (parameter_curve, parameter_curve_end) = curve_block(span, cur.pos())?;
cur.set_pos(parameter_curve_end);
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
let cache_fit_tolerance = Some(cur.take_f64()? * LEN_TO_MM);
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
let discontinuity_flag = cur.take_bool()?;
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Skin(Box::new(EmbeddedSkinSurface {
surface_boolean,
surface_normal,
surface_direction,
count,
parameter,
inner_count,
layout,
direction: Vector3::new(direction[0], direction[1], direction[2]),
trailing_parameter,
formula,
parameter_curve,
discontinuities,
discontinuity_flag,
})),
cache_fit_tolerance,
})
}
pub(crate) fn law_spl_sur(toks: &[Token]) -> Option<DecodedProceduralSurface> {
let names = ["law_spl_sur", "lawsur"];
let (start, _) = toks::find_owned_subtype_marker(toks, &names)?;
let span = toks::subtype_span(toks, start)?;
let mut cur = Cur::at(span, 2);
let parameter_ranges = if matches!(cur.peek(), Some(Token::Double(_))) {
Some([
[cur.take_f64()?, cur.take_f64()?],
[cur.take_f64()?, cur.take_f64()?],
])
} else {
None
};
let primary = law_formula(&mut cur)?;
let count = usize::try_from(cur.take_long()?).ok()?;
if count > 100_000 {
return None;
}
let additional = (0..count)
.map(|_| law_formula(&mut cur))
.collect::<Option<Vec<_>>>()?;
let selector = if parameter_ranges.is_some()
&& toks::marker_at(span, cur.pos()) == Some(toks::BsplineMarker::Nubs)
{
0
} else {
cur.take_enum()?
};
let (tail, cache_fit_tolerance) = match selector {
0 => {
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
(
cadmpeg_ir::geometry::LawSurfaceTail::Full,
Some(cur.take_f64()? * LEN_TO_MM),
)
}
1 => {
let parameters = [cur.take_float_array()?, cur.take_float_array()?];
let fit_tolerance = cur.take_f64()? * LEN_TO_MM;
let closures = [cur.take_enum()?, cur.take_enum()?];
let singularities = [cur.take_enum()?, cur.take_enum()?];
(
cadmpeg_ir::geometry::LawSurfaceTail::Summary {
parameters,
fit_tolerance,
closures,
singularities,
},
None,
)
}
2 => {
let parameter_ranges = [
[cur.take_f64()?, cur.take_f64()?],
[cur.take_f64()?, cur.take_f64()?],
];
let closures = [cur.take_enum()?, cur.take_enum()?];
let singularities = [cur.take_enum()?, cur.take_enum()?];
(
cadmpeg_ir::geometry::LawSurfaceTail::None {
parameter_ranges,
closures,
singularities,
},
None,
)
}
3 => (cadmpeg_ir::geometry::LawSurfaceTail::Historical, None),
4 => (cadmpeg_ir::geometry::LawSurfaceTail::Optimal, None),
_ => return None,
};
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Law(Box::new(EmbeddedLawSurface {
parameter_ranges,
primary,
additional,
tail,
discontinuities,
})),
cache_fit_tolerance,
})
}
pub(crate) fn sub_spl_sur(toks: &[Token]) -> Option<DecodedProceduralSurface> {
let names = ["sub_spl_sur", "subsur"];
let (start, _) = toks::find_owned_subtype_marker(toks, &names)?;
let span = toks::subtype_span(toks, start)?;
let mut cur = Cur::at(span, 2);
let parameter_ranges = [
[cur.take_f64()?, cur.take_f64()?],
[cur.take_f64()?, cur.take_f64()?],
];
let support = embedded_surface(&mut cur)?;
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::SubSurface {
support,
parameter_ranges,
},
cache_fit_tolerance: None,
})
}
fn net_spl_sur(toks: &[Token]) -> Option<DecodedProceduralSurface> {
let names = ["net_spl_sur", "netsur"];
let (start, _) = toks::find_owned_subtype_marker(toks, &names)?;
let span = toks::subtype_span(toks, start)?;
let mut cur = Cur::at(span, 2);
let sections = Box::new([loft_section(&mut cur)?, loft_section(&mut cur)?]);
let mut frame_parameters = [0.0; 12];
for parameter in &mut frame_parameters {
*parameter = cur.take_f64()?;
}
let flag = cur.take_long()?;
let mut directions = [Vector3::new(0.0, 0.0, 0.0); 4];
for direction in &mut directions {
let value = cur.take_vector3()?;
*direction = Vector3::new(value[0], value[1], value[2]);
}
let formulas = (0..4)
.map(|_| law_formula(&mut cur))
.collect::<Option<Vec<_>>>()?
.try_into()
.ok()?;
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
let cache_fit_tolerance = Some(cur.take_f64()? * LEN_TO_MM);
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
let discontinuity_flag = cur.take_bool()?;
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Net(Box::new(EmbeddedNetSurface {
sections,
frame_parameters,
flag,
directions,
formulas: Box::new(formulas),
discontinuities,
discontinuity_flag,
})),
cache_fit_tolerance,
})
}
fn sweep_spl_sur(
toks: &[Token],
resolver: Option<&SubtypeTable>,
) -> Option<DecodedProceduralSurface> {
let names = ["sweep_spl_sur", "sweep_sur", "sweepsur"];
let (start, name) = toks::find_owned_subtype_marker(toks, &names)?;
let span = toks::subtype_span(toks, start)?;
let mut cur = Cur::at(span, 2);
if matches!(cur.peek(), Some(Token::Long(_))) {
(name == "sweep_sur").then_some(())?;
return revision_sweep_sur(span, cur.pos(), resolver?);
}
let primary_kind = cur.take_enum()?;
let layout = if cur.peek().is_some_and(Token::is_payload_ident) {
let (profile, profile_end) = curve_block(span, cur.pos())?;
cur.set_pos(profile_end);
let (spine, spine_end) = curve_block(span, cur.pos())?;
cur.set_pos(spine_end);
let secondary_kind = cur.take_enum()?;
let mut directions = [Vector3::new(0.0, 0.0, 0.0); 5];
for direction in &mut directions {
let value = cur.take_vector3()?;
*direction = Vector3::new(value[0], value[1], value[2]);
}
let origin = cur.take_position()?;
let mut parameters = [0.0; 4];
for parameter in &mut parameters {
*parameter = cur.take_f64()?;
}
let formulas = (0..3)
.map(|_| law_formula(&mut cur))
.collect::<Option<Vec<_>>>()?
.try_into()
.ok()?;
EmbeddedSweepSurfaceLayout::ProfileFirst {
profile,
spine,
secondary_kind,
directions,
origin: Point3::new(
origin[0] * LEN_TO_MM,
origin[1] * LEN_TO_MM,
origin[2] * LEN_TO_MM,
),
parameters,
formulas: Box::new(formulas),
}
} else {
let mode = cur.take_long()?;
let (profile, profile_end) = curve_block(span, cur.pos())?;
cur.set_pos(profile_end);
let profile_range = [cur.take_f64()?, cur.take_f64()?];
let profile_frame = if cur.take_bool()? {
let point = cur.take_position()?;
let vector = cur.take_vector3()?;
Some((
Point3::new(
point[0] * LEN_TO_MM,
point[1] * LEN_TO_MM,
point[2] * LEN_TO_MM,
),
Vector3::new(vector[0], vector[1], vector[2]),
))
} else {
None
};
let point = cur.take_position()?;
let origin = Point3::new(
point[0] * LEN_TO_MM,
point[1] * LEN_TO_MM,
point[2] * LEN_TO_MM,
);
let mut directions = [Vector3::new(0.0, 0.0, 0.0); 3];
for direction in &mut directions {
let value = cur.take_vector3()?;
*direction = Vector3::new(value[0], value[1], value[2]);
}
if matches!(cur.peek(), Some(Token::Long(_))) {
let branch = cur.take_long()?;
let trajectory_flag = cur.take_bool()?;
let (path, path_end) = curve_block(span, cur.pos())?;
cur.set_pos(path_end);
let path_range = [cur.take_f64()? * LEN_TO_MM, cur.take_f64()? * LEN_TO_MM];
let path_parameter = cur.take_f64()?;
match branch {
1 => {
let formula_flag = cur.take_bool()?;
let formula = law_formula(&mut cur)?;
let trailing_flag = cur.take_bool()?;
EmbeddedSweepSurfaceLayout::ExplicitFormula {
profile,
mode,
profile_range,
profile_frame,
origin,
directions,
trajectory_flag,
path,
path_range,
path_parameter,
formula_flag,
formula,
trailing_flag,
}
}
2 => {
let guide_flags = [cur.take_bool()?, cur.take_bool()?];
let (guide_curve, guide_end) = curve_block(span, cur.pos())?;
cur.set_pos(guide_end);
let guide_range = [cur.take_f64()?, cur.take_f64()?];
let guide_modes = [cur.take_long()?, cur.take_long()?];
let mut guide_parameters = [0.0; 6];
for parameter in &mut guide_parameters {
*parameter = cur.take_f64()?;
}
let trailing_flags = [cur.take_bool()?, cur.take_bool()?, cur.take_bool()?];
EmbeddedSweepSurfaceLayout::ExplicitGuide {
profile,
mode,
profile_range,
profile_frame,
origin,
directions,
trajectory_flag,
path,
path_range,
path_parameter,
guide_flags,
guide_curve,
guide_range,
guide_modes,
guide_parameters,
trailing_flags,
}
}
3 => {
let singularity = cur.take_enum()?;
let support_surface = embedded_surface(&mut cur)?;
let auxiliary_curve = if cur.take_bool()? {
let (curve, curve_end) = curve_block(span, cur.pos())?;
cur.set_pos(curve_end);
Some(curve)
} else {
None
};
let support_flag = cur.take_bool()?;
let legacy_flag = matches!(cur.peek(), Some(Token::True | Token::False))
.then(|| cur.take_bool())
.flatten();
EmbeddedSweepSurfaceLayout::ExplicitSurface {
profile,
mode,
profile_range,
profile_frame,
origin,
directions,
trajectory_flag,
path,
path_range,
path_parameter,
singularity,
support_surface,
auxiliary_curve,
support_flag,
legacy_flag,
}
}
_ => return None,
}
} else {
let first_law = sweep_law_expression(&mut cur)?;
let first_mode = cur.take_long()?;
let first_range = [cur.take_f64()?, cur.take_f64()?];
let vector = cur.take_vector3()?;
let law_direction = Vector3::new(vector[0], vector[1], vector[2]);
let path_mode = cur.take_long()?;
let path_flag = cur.take_bool()?;
let (path, path_end) = curve_block(span, cur.pos())?;
cur.set_pos(path_end);
let path_range = [cur.take_f64()?, cur.take_f64()?];
let path_parameter = cur.take_f64()?;
let second_law_flag = cur.take_bool()?;
let second_law = sweep_law_expression(&mut cur)?;
let formula_mode = cur.take_long()?;
let formula = law_formula(&mut cur)?;
let trailing_flag = cur.take_bool()?;
EmbeddedSweepSurfaceLayout::LawDriven {
profile,
mode,
profile_range,
profile_frame,
origin,
directions,
first_law,
first_mode,
first_range,
law_direction,
path_mode,
path_flag,
path,
path_range,
path_parameter,
second_law_flag,
second_law,
formula_mode,
formula,
trailing_flag,
}
}
};
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
let cache_fit_tolerance = Some(cur.take_f64()? * LEN_TO_MM);
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
let discontinuity_flag = cur.take_bool()?;
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Sweep(Box::new(EmbeddedSweepSurface {
primary_kind,
revision_form: None,
layout,
discontinuities,
discontinuity_flag,
})),
cache_fit_tolerance,
})
}
fn revision_sweep_sur(
span: &[Token],
position: usize,
table: &SubtypeTable,
) -> Option<DecodedProceduralSurface> {
let mut cur = Cur::at(span, position);
let revision = cur.take_long()?;
(revision > 0).then_some(())?;
let primary_flag = cur.take_bool()?;
let mode = cur.take_long()?;
let profile = embedded_base_curve_resolving_refs(&mut cur, table)?;
let profile_endpoints = [
cur.take_optional_range_value()?,
cur.take_optional_range_value()?,
];
let profile_range = [
cur.take_optional_range_value()??,
cur.take_optional_range_value()??,
];
let profile_frame = if cur.take_bool()? {
let point = cur.take_position()?;
let vector = cur.take_vector3()?;
Some((
Point3::new(
point[0] * LEN_TO_MM,
point[1] * LEN_TO_MM,
point[2] * LEN_TO_MM,
),
Vector3::new(vector[0], vector[1], vector[2]),
))
} else {
None
};
let point = cur.take_position()?;
let origin = Point3::new(
point[0] * LEN_TO_MM,
point[1] * LEN_TO_MM,
point[2] * LEN_TO_MM,
);
let mut directions = [Vector3::new(0.0, 0.0, 0.0); 3];
for direction in &mut directions {
let value = cur.take_vector3()?;
*direction = Vector3::new(value[0], value[1], value[2]);
}
let (layout, path_endpoints) = if matches!(cur.peek(), Some(Token::Str(_))) {
let first_law = sweep_law_expression(&mut cur)?;
let first_mode = cur.take_long()?;
let first_range = [
cur.take_optional_range_value()??,
cur.take_optional_range_value()??,
];
let law_direction = cur.take_vector3()?;
let path_mode = cur.take_long()?;
let path_flag = cur.take_bool()?;
let path = embedded_base_curve_resolving_refs(&mut cur, table)?;
let path_endpoints = [
cur.take_optional_range_value()?,
cur.take_optional_range_value()?,
];
let path_range = [
cur.take_optional_range_value()?? * LEN_TO_MM,
cur.take_optional_range_value()?? * LEN_TO_MM,
];
let path_parameter = cur.take_f64()?;
let second_law_flag = cur.take_bool()?;
let second_law = sweep_law_expression(&mut cur)?;
let formula_mode = cur.take_long()?;
let formula = law_formula_resolving(&mut cur, Some(table))?;
let trailing_flag = cur.take_bool()?;
let law_direction = Vector3::new(law_direction[0], law_direction[1], law_direction[2]);
(
EmbeddedSweepSurfaceLayout::LawDriven {
profile,
mode,
profile_range,
profile_frame,
origin,
directions,
first_law,
first_mode,
first_range,
law_direction,
path_mode,
path_flag,
path,
path_range,
path_parameter,
second_law_flag,
second_law,
formula_mode,
formula,
trailing_flag,
},
path_endpoints,
)
} else {
(cur.take_long()? == 1).then_some(())?;
let trajectory_flag = cur.take_bool()?;
let path = embedded_base_curve_resolving_refs(&mut cur, table)?;
let path_endpoints = [
cur.take_optional_range_value()?,
cur.take_optional_range_value()?,
];
let path_range = [
cur.take_optional_range_value()?? * LEN_TO_MM,
cur.take_optional_range_value()?? * LEN_TO_MM,
];
let path_parameter = cur.take_f64()?;
let formula_flag = cur.take_bool()?;
let formula = law_formula_resolving(&mut cur, Some(table))?;
let trailing_flag = cur.take_bool()?;
(
EmbeddedSweepSurfaceLayout::ExplicitFormula {
profile,
mode,
profile_range,
profile_frame,
origin,
directions,
trajectory_flag,
path,
path_range,
path_parameter,
formula_flag,
formula,
trailing_flag,
},
path_endpoints,
)
};
let RevisionSurfaceTail {
enumeration: tail_enum,
fit_tolerance: cache_fit_tolerance,
solved_cache_domains: _,
parameterization: tail_parameterization,
discontinuities,
tail_flag: discontinuity_flag,
} = revision_surface_tail(&mut cur)?;
cur.at_scope_end().then_some(())?;
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Sweep(Box::new(EmbeddedSweepSurface {
primary_kind: 0,
revision_form: Some(cadmpeg_ir::geometry::SweepRevisionForm {
revision,
primary_flag,
profile_endpoints,
path_endpoints,
tail_enum,
tail_parameterization,
}),
layout,
discontinuities,
discontinuity_flag,
})),
cache_fit_tolerance,
})
}
fn taper_spl_sur(
toks: &[Token],
resolver: Option<&SubtypeTable>,
) -> Option<DecodedProceduralSurface> {
use cadmpeg_ir::geometry::TaperSurfaceKind;
let names: &[(&str, u8)] = &[
("taper_spl_sur", 0),
("ortho_spl_sur", 1),
("orthosur", 1),
("edge_tpr_spl_sur", 2),
("shadow_tpr_spl_sur", 3),
("shadowtapersur", 3),
("ruled_tpr_spl_sur", 4),
("ruledtapersur", 4),
("swept_tpr_spl_sur", 5),
("swepttapersur", 5),
];
let candidates: Vec<&str> = names.iter().map(|(name, _)| *name).collect();
let (start, name) = toks::find_owned_subtype_marker(toks, &candidates)?;
let kind = names
.iter()
.find_map(|(candidate, kind)| (*candidate == name).then_some(*kind))?;
let span = toks::subtype_span(toks, start)?;
let mut cur = Cur::at(span, 2);
if matches!(cur.peek(), Some(Token::Long(_))) {
(name == "ortho_spl_sur").then_some(())?;
let table = resolver?;
let revision = cur.take_long()?;
(revision > 0).then_some(())?;
let (support, support_bounds) = optional_embedded_surface_with_bounds(&mut cur, table)?;
let support = support?;
let reference = embedded_base_curve_resolving_refs(&mut cur, table)?;
let reference_endpoints = [
cur.take_optional_range_value()?,
cur.take_optional_range_value()?,
];
let pcurve = nullable_embedded_pcurve(&mut cur)?;
let parameter = cur.take_f64()?;
let RevisionSurfaceTail {
enumeration: tail_enum,
fit_tolerance,
solved_cache_domains: _,
parameterization,
discontinuities,
tail_flag,
} = revision_surface_tail(&mut cur)?;
let sense = cur.take_bool()?;
cur.at_scope_end().then_some(())?;
return Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Taper {
support,
reference,
pcurve,
parameter,
taper: TaperSurfaceKind::Orthogonal { sense },
revision_form: Some(cadmpeg_ir::geometry::RevisionSurfaceForm {
revision,
support_bounds,
reference_endpoints,
second_endpoints: [None; 2],
flags: Vec::new(),
tail_enum,
tail_parameterization: parameterization,
discontinuities,
tail_flag,
trailing_flags: Vec::new(),
}),
},
cache_fit_tolerance: fit_tolerance,
});
}
let support = embedded_surface(&mut cur)?;
let (reference, reference_end) = curve_block(span, cur.pos())?;
cur.set_pos(reference_end);
let saved = cur.pos();
let pcurve = if cur.take_ident() == Some("nullbs") {
None
} else {
cur.set_pos(saved);
let (pcurve, end) = pcurve_block_with_end(span, cur.pos())?;
cur.set_pos(end);
Some(pcurve)
};
let parameter = cur.take_f64()?;
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
let cache_fit_tolerance = if matches!(cur.peek(), Some(Token::Double(_))) {
Some(cur.take_f64()? * LEN_TO_MM)
} else {
None
};
let take_draft = |cur: &mut Cur<'_>| {
let draft = cur.take_vector3()?;
Some(Vector3::new(draft[0], draft[1], draft[2]))
};
let taper = match kind {
0 => TaperSurfaceKind::Standard,
1 => TaperSurfaceKind::Orthogonal {
sense: cur.take_bool()?,
},
2 => TaperSurfaceKind::Edge {
draft: take_draft(&mut cur)?,
},
3 => TaperSurfaceKind::Shadow {
draft: take_draft(&mut cur)?,
sine: cur.take_f64()?,
cosine: cur.take_f64()?,
},
4 => TaperSurfaceKind::Ruled {
draft: take_draft(&mut cur)?,
sine: cur.take_f64()?,
cosine: cur.take_f64()?,
factor: cur.take_f64()?,
},
5 => TaperSurfaceKind::Swept {
draft: take_draft(&mut cur)?,
sine: cur.take_f64()?,
cosine: cur.take_f64()?,
},
_ => return None,
};
cur.at_scope_end().then_some(())?;
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Taper {
support,
reference,
pcurve,
parameter,
taper,
revision_form: None,
},
cache_fit_tolerance,
})
}
fn comp_spl_sur(toks: &[Token]) -> Option<DecodedProceduralSurface> {
let (start, _) = toks::find_owned_subtype_marker(toks, &["comp_spl_sur"])?;
let span = toks::subtype_span(toks, start)?;
let mut cur = Cur::at(span, 2);
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
let cache_fit_tolerance = if matches!(cur.peek(), Some(Token::Double(_))) {
Some(cur.take_f64()? * LEN_TO_MM)
} else {
None
};
let parameters = cur.take_float_array()?;
let mut components = Vec::with_capacity(parameters.len());
for _ in 0..parameters.len() {
components.push(embedded_surface(&mut cur)?);
}
cur.at_scope_end().then_some(())?;
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Compound {
parameters,
components,
},
cache_fit_tolerance,
})
}
pub struct RevisionSurfaceTail {
pub enumeration: i64,
pub fit_tolerance: Option<f64>,
pub solved_cache_domains: Option<[[f64; 2]; 2]>,
pub parameterization: Option<cadmpeg_ir::geometry::RevisionSurfaceParameterization>,
pub discontinuities: [Vec<f64>; 6],
pub tail_flag: bool,
}
pub fn revision_surface_tail(cur: &mut Cur<'_>) -> Option<RevisionSurfaceTail> {
let enumeration = cur.take_enum()?;
let (fit_tolerance, solved_cache_domains, parameterization) = match enumeration {
0 => {
let (cache, cache_end) = surface_block(cur.toks(), cur.pos())?;
cur.set_pos(cache_end);
let domains = [
[*cache.u_knots.first()?, *cache.u_knots.last()?],
[*cache.v_knots.first()?, *cache.v_knots.last()?],
];
(Some(cur.take_f64()? * LEN_TO_MM), Some(domains), None)
}
2 => {
let u_interval = [
cur.take_optional_range_value()?,
cur.take_optional_range_value()?,
];
let v_interval = [
cur.take_optional_range_value()?,
cur.take_optional_range_value()?,
];
(
None,
None,
Some(cadmpeg_ir::geometry::RevisionSurfaceParameterization {
u_interval,
v_interval,
u_closure: cur.take_enum()?,
v_closure: cur.take_enum()?,
u_singularity: cur.take_enum()?,
v_singularity: cur.take_enum()?,
}),
)
}
_ => return None,
};
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
let tail_flag = cur.take_bool()?;
Some(RevisionSurfaceTail {
enumeration,
fit_tolerance,
solved_cache_domains,
parameterization,
discontinuities,
tail_flag,
})
}
fn off_spl_sur(
toks: &[Token],
resolver: Option<&SubtypeTable>,
) -> Option<DecodedProceduralSurface> {
let names = ["off_spl_sur", "offsur"];
let (start, name) = toks::find_owned_subtype_marker(toks, &names)?;
let modern = name == "off_spl_sur";
let span = toks::subtype_span(toks, start)?;
let mut cur = Cur::at(span, 2);
if matches!(cur.peek(), Some(Token::Long(_))) {
modern.then_some(())?;
let table = resolver?;
let revision = cur.take_long()?;
(revision > 0).then_some(())?;
let (support, support_bounds) = optional_embedded_surface_with_bounds(&mut cur, table)?;
let support = support?;
let distance = cur.take_f64()? * LEN_TO_MM;
let mut flags = Vec::with_capacity(4);
for _ in 0..4 {
flags.push(cur.take_bool()?);
}
let RevisionSurfaceTail {
enumeration: tail_enum,
fit_tolerance,
solved_cache_domains: _,
parameterization,
discontinuities,
tail_flag,
} = revision_surface_tail(&mut cur)?;
cur.at_scope_end().then_some(())?;
return Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Offset {
support,
distance,
u_sense: None,
v_sense: None,
extension_flags: Vec::new(),
revision_form: Some(cadmpeg_ir::geometry::RevisionSurfaceForm {
revision,
support_bounds,
reference_endpoints: [None; 2],
second_endpoints: [None; 2],
flags,
tail_enum,
tail_parameterization: parameterization,
discontinuities,
tail_flag,
trailing_flags: Vec::new(),
}),
},
cache_fit_tolerance: fit_tolerance,
});
}
let support = embedded_surface(&mut cur)?;
let distance = cur.take_f64()? * LEN_TO_MM;
let u_sense = Some(cur.take_enum()?);
let v_sense = Some(cur.take_enum()?);
let mut extension_flags = Vec::new();
if modern {
let first = cur.take_bool()?;
extension_flags.push(first);
if first {
extension_flags.push(cur.take_bool()?);
if matches!(cur.peek(), Some(Token::True | Token::False)) {
extension_flags.push(cur.take_bool()?);
}
}
}
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
let cache_fit_tolerance = optional_trailing_cache_tolerance(&mut cur)?;
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Offset {
support,
distance,
u_sense,
v_sense,
extension_flags,
revision_form: None,
},
cache_fit_tolerance,
})
}
fn rot_spl_sur(
toks: &[Token],
resolver: Option<&SubtypeTable>,
) -> Option<DecodedProceduralSurface> {
let names = ["rot_spl_sur", "rotsur"];
let (start, name) = toks::find_owned_subtype_marker(toks, &names)?;
let span = toks::subtype_span(toks, start)?;
let mut cur = Cur::at(span, 2);
if matches!(cur.peek(), Some(Token::Long(_))) {
(name == "rot_spl_sur").then_some(())?;
let revision = cur.take_long()?;
(revision > 0).then_some(())?;
let table = resolver?;
let profile = embedded_base_curve_resolving_refs(&mut cur, table)?;
let profile_endpoints = [
cur.take_optional_range_value()?,
cur.take_optional_range_value()?,
];
let origin = cur.take_position()?;
let axis = cur.take_vector3()?;
let RevisionSurfaceTail {
enumeration: tail_enum,
fit_tolerance,
solved_cache_domains,
parameterization,
discontinuities,
tail_flag,
} = revision_surface_tail(&mut cur)?;
cur.at_scope_end().then_some(())?;
let angular_interval = solved_cache_domains?[1];
let parameter_interval = [
profile_endpoints[0].unwrap_or(*profile.knots.first()?),
profile_endpoints[1].unwrap_or(*profile.knots.last()?),
];
return Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Revolution {
directrix: CurveGeometry::Nurbs(profile),
axis_origin: Point3::new(
origin[0] * LEN_TO_MM,
origin[1] * LEN_TO_MM,
origin[2] * LEN_TO_MM,
),
axis_direction: normalized(axis)?,
angular_interval,
parameter_interval,
revision_form: Some(cadmpeg_ir::geometry::RevisionSurfaceForm {
revision,
support_bounds: [None; 4],
reference_endpoints: profile_endpoints,
second_endpoints: [None; 2],
flags: Vec::new(),
tail_enum,
tail_parameterization: parameterization,
discontinuities,
tail_flag,
trailing_flags: Vec::new(),
}),
},
cache_fit_tolerance: fit_tolerance,
});
}
let (directrix, directrix_end) = curve_block(span, cur.pos())?;
cur.set_pos(directrix_end);
let parameter_interval = [*directrix.knots.first()?, *directrix.knots.last()?];
let origin = cur.take_position()?;
let axis_origin = Point3::new(
origin[0] * LEN_TO_MM,
origin[1] * LEN_TO_MM,
origin[2] * LEN_TO_MM,
);
let axis = cur.take_vector3()?;
let axis_direction = normalized(axis)?;
let (cache, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
let angular_interval = [*cache.v_knots.first()?, *cache.v_knots.last()?];
let cache_fit_tolerance = optional_trailing_cache_tolerance(&mut cur)?;
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Revolution {
directrix: CurveGeometry::Nurbs(directrix),
axis_origin,
axis_direction,
angular_interval,
parameter_interval,
revision_form: None,
},
cache_fit_tolerance,
})
}
fn sum_spl_sur(
toks: &[Token],
resolver: Option<&SubtypeTable>,
) -> Option<DecodedProceduralSurface> {
let names = ["sum_spl_sur", "sumsur"];
let (start, name) = toks::find_owned_subtype_marker(toks, &names)?;
let span = toks::subtype_span(toks, start)?;
let mut cur = Cur::at(span, 2);
if matches!(cur.peek(), Some(Token::Long(_))) {
(name == "sum_spl_sur").then_some(())?;
let revision = cur.take_long()?;
(revision > 0).then_some(())?;
let table = resolver?;
let first = embedded_base_curve_resolving_refs(&mut cur, table)?;
let first_endpoints = [
cur.take_optional_range_value()?,
cur.take_optional_range_value()?,
];
let second = embedded_base_curve_resolving_refs(&mut cur, table)?;
let second_endpoints = [
cur.take_optional_range_value()?,
cur.take_optional_range_value()?,
];
let origin = cur.take_position()?;
let RevisionSurfaceTail {
enumeration: tail_enum,
fit_tolerance,
solved_cache_domains: _,
parameterization,
discontinuities,
tail_flag,
} = revision_surface_tail(&mut cur)?;
cur.at_scope_end().then_some(())?;
return Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Sum {
first: CurveGeometry::Nurbs(first),
second: CurveGeometry::Nurbs(second),
basepoint: Vector3::new(
origin[0] * LEN_TO_MM,
origin[1] * LEN_TO_MM,
origin[2] * LEN_TO_MM,
),
revision_form: Some(cadmpeg_ir::geometry::RevisionSurfaceForm {
revision,
support_bounds: [None; 4],
reference_endpoints: first_endpoints,
second_endpoints,
flags: Vec::new(),
tail_enum,
tail_parameterization: parameterization,
discontinuities,
tail_flag,
trailing_flags: Vec::new(),
}),
},
cache_fit_tolerance: fit_tolerance,
});
}
let (first, first_end) = curve_block(span, cur.pos())?;
cur.set_pos(first_end);
let (second, second_end) = curve_block(span, cur.pos())?;
cur.set_pos(second_end);
let origin = cur.take_position()?;
let basepoint = Vector3::new(
origin[0] * LEN_TO_MM,
origin[1] * LEN_TO_MM,
origin[2] * LEN_TO_MM,
);
let cache_fit_tolerance = if cur.at_scope_end() {
None
} else {
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
optional_trailing_cache_tolerance(&mut cur)?
};
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Sum {
first: CurveGeometry::Nurbs(first),
second: CurveGeometry::Nurbs(second),
basepoint,
revision_form: None,
},
cache_fit_tolerance,
})
}
fn ruled_spl_sur(toks: &[Token]) -> Option<DecodedProceduralSurface> {
let names = ["rule_sur", "rulesur"];
let (start, _) = toks::find_owned_subtype_marker(toks, &names)?;
let span = toks::subtype_span(toks, start)?;
let mut cur = Cur::at(span, 2);
let (first, first_end) = curve_block(span, cur.pos())?;
cur.set_pos(first_end);
let (second, second_end) = curve_block(span, cur.pos())?;
cur.set_pos(second_end);
let cache_fit_tolerance = if cur.at_scope_end() {
None
} else {
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
optional_trailing_cache_tolerance(&mut cur)?
};
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Ruled { first, second },
cache_fit_tolerance,
})
}
fn exact_spl_sur(toks: &[Token]) -> Option<DecodedProceduralSurface> {
let names = ["exact_spl_sur", "exactsur"];
let (start, name) = toks::find_owned_subtype_marker(toks, &names)?;
let span = toks::subtype_span(toks, start)?;
let mut cur = Cur::at(span, 2);
if matches!(cur.peek(), Some(Token::Long(_))) {
(name == "exact_spl_sur").then_some(())?;
let revision = cur.take_long()?;
(revision > 0).then_some(())?;
let RevisionSurfaceTail {
enumeration: tail_enum,
fit_tolerance,
solved_cache_domains: _,
parameterization,
discontinuities,
tail_flag,
} = revision_surface_tail(&mut cur)?;
let unextended_ranges = [
[
cur.take_optional_range_value()?,
cur.take_optional_range_value()?,
],
[
cur.take_optional_range_value()?,
cur.take_optional_range_value()?,
],
];
let extension = cur.take_enum()?;
cur.at_scope_end().then_some(())?;
return Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Exact {
parameters: cadmpeg_ir::geometry::SplineSurfaceParameters::RevisionRanges {
intervals: unextended_ranges,
},
extension,
revision_form: Some(cadmpeg_ir::geometry::RevisionSurfaceForm {
revision,
support_bounds: [None; 4],
reference_endpoints: [None; 2],
second_endpoints: [None; 2],
flags: Vec::new(),
tail_enum,
tail_parameterization: parameterization,
discontinuities,
tail_flag,
trailing_flags: Vec::new(),
}),
},
cache_fit_tolerance: fit_tolerance,
});
}
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
let cache_fit_tolerance = Some(cur.take_f64()? * LEN_TO_MM);
let parameter_ranges = [
[cur.take_range_value()?, cur.take_range_value()?],
[cur.take_range_value()?, cur.take_range_value()?],
];
let extension = cur.take_long()?;
cur.at_scope_end().then_some(())?;
let _ = name;
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Exact {
parameters: cadmpeg_ir::geometry::SplineSurfaceParameters::OrderedRanges {
ranges: parameter_ranges,
},
extension,
revision_form: None,
},
cache_fit_tolerance,
})
}
fn t_spl_sur(toks: &[Token]) -> Option<DecodedProceduralSurface> {
use cadmpeg_ir::geometry::{TSplineSubtransform, TSplineSurfaceConstruction};
let (start, _) = toks::find_owned_subtype_marker(toks, &["t_spl_sur"])?;
let span = toks::subtype_span(toks, start)?;
let mut cur = Cur::at(span, 2);
let (
cache_fit_tolerance,
discontinuities,
discontinuity_flag,
parameter_ranges,
type_code,
revision_form,
);
if matches!(cur.peek(), Some(Token::Long(_))) {
let revision = cur.take_long()?;
(revision > 0).then_some(())?;
let RevisionSurfaceTail {
enumeration: tail_enum,
fit_tolerance,
solved_cache_domains: _,
parameterization,
discontinuities: tail_discontinuities,
tail_flag,
} = revision_surface_tail(&mut cur)?;
let mut bounds = [None; 4];
for bound in &mut bounds {
*bound = cur.take_optional_range_value()?;
}
cache_fit_tolerance = fit_tolerance;
discontinuities = tail_discontinuities.clone();
discontinuity_flag = tail_flag;
parameter_ranges = [
[bounds[0].unwrap_or(0.0), bounds[1].unwrap_or(0.0)],
[bounds[2].unwrap_or(0.0), bounds[3].unwrap_or(0.0)],
];
type_code = cur.take_enum()?;
revision_form = Some(cadmpeg_ir::geometry::RevisionSurfaceForm {
revision,
support_bounds: bounds,
reference_endpoints: [None; 2],
second_endpoints: [None; 2],
flags: Vec::new(),
tail_enum,
tail_parameterization: parameterization,
discontinuities: tail_discontinuities,
tail_flag,
trailing_flags: Vec::new(),
});
} else {
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
cache_fit_tolerance = Some(cur.take_f64()? * LEN_TO_MM);
discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
discontinuity_flag = cur.take_bool()?;
parameter_ranges = [
[cur.take_f64()? * LEN_TO_MM, cur.take_f64()? * LEN_TO_MM],
[cur.take_f64()? * LEN_TO_MM, cur.take_f64()? * LEN_TO_MM],
];
type_code = cur.take_long()?;
revision_form = None;
}
if !matches!(cur.peek(), Some(Token::SubtypeOpen)) {
return None;
}
cur.bump();
let source_kind = cur.take_ident()?;
let subtransform = match source_kind {
"t_spl_subtrans_object" => {
let program = cur.take_str()?.to_string();
let separator = if matches!(cur.peek(), Some(Token::Str(_))) {
None
} else {
Some(cur.take_bool()?)
};
let values = cur.take_str()?.to_string();
TSplineSubtransform::Inline {
program,
separator,
values,
}
}
"ref" => TSplineSubtransform::Reference {
index: cur.take_long()?,
resolved: None,
},
_ => return None,
};
if !matches!(cur.peek(), Some(Token::SubtypeClose)) {
return None;
}
cur.bump();
let trailing_value = cur.take_long()?;
cur.at_scope_end().then_some(())?;
let program_graph = match &subtransform {
TSplineSubtransform::Inline { program, .. } => {
Some(cadmpeg_ir::geometry::TSplineProgram::parse(program))
}
TSplineSubtransform::Reference { .. } => None,
};
let values_graph = match &subtransform {
TSplineSubtransform::Inline { values, .. } => {
Some(cadmpeg_ir::geometry::TSplineProgram::parse(values))
}
TSplineSubtransform::Reference { .. } => None,
};
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::TSpline(Box::new(
TSplineSurfaceConstruction {
parameter_ranges,
type_code,
subtransform,
program_graph,
values_graph,
trailing_value,
discontinuities,
discontinuity_flag,
revision_form,
},
)),
cache_fit_tolerance,
})
}
fn deformable_surface_frame(
cur: &mut Cur<'_>,
) -> Option<cadmpeg_ir::geometry::DeformableSurfaceFrame> {
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 mut secondary_vectors = [Vector3::new(0.0, 0.0, 0.0); 3];
for vector in &mut secondary_vectors {
let value = cur.take_vector3()?;
*vector = Vector3::new(value[0], value[1], value[2]);
}
let secondary_parameter = cur.take_f64()?;
let secondary_flags = [cur.take_bool()?, cur.take_bool()?];
let point = cur.take_position()?;
let trailing_flags = [
cur.take_bool()?,
cur.take_bool()?,
cur.take_bool()?,
cur.take_bool()?,
cur.take_bool()?,
];
Some(cadmpeg_ir::geometry::DeformableSurfaceFrame {
leading_vectors,
leading_parameter,
leading_flags,
secondary_vectors,
secondary_parameter,
secondary_flags,
point: Point3::new(
point[0] * LEN_TO_MM,
point[1] * LEN_TO_MM,
point[2] * LEN_TO_MM,
),
trailing_flags,
})
}
fn deformable_vector_frame(
cur: &mut Cur<'_>,
) -> Option<cadmpeg_ir::geometry::DeformableVectorFrame> {
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]);
}
Some(cadmpeg_ir::geometry::DeformableVectorFrame {
vectors,
parameter: cur.take_f64()?,
flags: [cur.take_bool()?, cur.take_bool()?, cur.take_bool()?],
})
}
fn revision_deformable_mode3(
cur: &mut Cur<'_>,
) -> Option<cadmpeg_ir::geometry::DeformableSurfaceData> {
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 point = cur.take_position()?;
let trailing_point = Point3::new(
point[0] * LEN_TO_MM,
point[1] * LEN_TO_MM,
point[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()?;
Some(cadmpeg_ir::geometry::DeformableSurfaceData::RevisionMode3 {
leading_vectors,
leading_parameter,
leading_flags,
trailing_point,
trailing_vectors,
frame_parameter,
frame_flags,
parameters,
trailing_flags,
trailing_parameter,
trailing_value,
})
}
fn defm_spl_sur(toks: &[Token]) -> Option<DecodedProceduralSurface> {
use cadmpeg_ir::geometry::DeformableSurfaceData;
let names = ["defm_spl_sur", "defmsur"];
let (start, _) = toks::find_owned_subtype_marker(toks, &names)?;
let span = toks::subtype_span(toks, start)?;
let mut cur = Cur::at(span, 2);
let (support, revision_form_head) = if matches!(cur.peek(), Some(Token::Long(_))) {
let revision = cur.take_long()?;
(revision == 22_506).then_some(())?;
let (support, ranges) = embedded_surface_with_ranges(&mut cur)?;
let support_bounds = [ranges[0][0], ranges[0][1], ranges[1][0], ranges[1][1]];
(support, Some((revision, support_bounds)))
} else {
(embedded_surface(&mut cur)?, None)
};
let mode = cur.take_long()?;
let data = match mode {
1 => {
let frame = Box::new(deformable_surface_frame(&mut cur)?);
let count = usize::try_from(cur.take_long()?).ok()?;
let parameter_triples = (0..count)
.map(|_| Some([cur.take_f64()?, cur.take_f64()?, cur.take_f64()?]))
.collect::<Option<Vec<_>>>()?;
EmbeddedDeformableSurfaceData::Resolved(DeformableSurfaceData::Plain {
frame,
parameter_triples,
})
}
3 if revision_form_head.is_some() => {
EmbeddedDeformableSurfaceData::Resolved(revision_deformable_mode3(&mut cur)?)
}
3 => EmbeddedDeformableSurfaceData::Resolved(DeformableSurfaceData::Guided {
frame: Box::new(deformable_surface_frame(&mut cur)?),
selector: cur.take_long()?,
guide_parameter: cur.take_f64()?,
}),
5 => {
let surface = embedded_surface(&mut cur)?;
let native_id = cur.take_long()?;
let flag = cur.take_bool()?;
let first_parameter = cur.take_f64()?;
let selector = cur.take_long()?;
let second_parameter = cur.take_f64()?;
let (curve, curve_end) = curve_block(span, cur.pos())?;
cur.set_pos(curve_end);
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 frame_parameter = cur.take_f64()?;
let flags = [cur.take_bool()?, cur.take_bool()?, cur.take_bool()?];
let count = usize::try_from(cur.take_long()?).ok()?;
let parameter_triples = (0..count)
.map(|_| Some([cur.take_f64()?, cur.take_f64()?, cur.take_f64()?]))
.collect::<Option<Vec<_>>>()?;
EmbeddedDeformableSurfaceData::SurfaceCurve {
surface,
native_id,
flag,
first_parameter,
selector,
second_parameter,
curve,
vectors,
frame_parameter,
flags,
parameter_triples,
}
}
6 => {
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 selector = cur.take_long()?;
let surface = embedded_surface(&mut cur)?;
let native_id = cur.take_long()?;
let flag = cur.take_bool()?;
let first_parameter = cur.take_f64()?;
let version_value = matches!(cur.peek(), Some(Token::Long(_)))
.then(|| cur.take_long())
.flatten();
let second_parameter = cur.take_f64()?;
let (curve, curve_end) = curve_block(span, cur.pos())?;
cur.set_pos(curve_end);
let frames = Box::new([
deformable_vector_frame(&mut cur)?,
deformable_vector_frame(&mut cur)?,
]);
EmbeddedDeformableSurfaceData::Full {
leading_vectors,
leading_parameter,
leading_flags,
selector,
surface,
native_id,
flag,
first_parameter,
version_value,
second_parameter,
curve,
frames,
trailing_value: cur.take_long()?,
}
}
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]);
}
EmbeddedDeformableSurfaceData::Resolved(DeformableSurfaceData::Minimal {
vectors,
selector: cur.take_long()?,
})
}
_ => return None,
};
let (revision_form, cache_fit_tolerance, discontinuities, discontinuity_flag) =
if let Some((revision, support_bounds)) = revision_form_head {
let RevisionSurfaceTail {
enumeration: tail_enum,
fit_tolerance,
solved_cache_domains: _,
parameterization: tail_parameterization,
discontinuities,
tail_flag,
} = revision_surface_tail(&mut cur)?;
(
Some(cadmpeg_ir::geometry::RevisionSurfaceForm {
revision,
support_bounds,
reference_endpoints: [None; 2],
second_endpoints: [None; 2],
flags: Vec::new(),
tail_enum,
tail_parameterization,
discontinuities: discontinuities.clone(),
tail_flag,
trailing_flags: Vec::new(),
}),
fit_tolerance,
discontinuities,
tail_flag,
)
} else {
let (_, cache_end) = surface_block(span, cur.pos())?;
cur.set_pos(cache_end);
let cache_fit_tolerance = Some(cur.take_f64()? * LEN_TO_MM);
let discontinuities = [
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
cur.take_float_array()?,
];
let discontinuity_flag = cur.take_bool()?;
(
None,
cache_fit_tolerance,
discontinuities,
discontinuity_flag,
)
};
if revision_form.is_some() {
cur.at_scope_end().then_some(())?;
}
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Deformable(Box::new(
EmbeddedDeformableSurface {
support,
revision_form,
data,
discontinuities,
discontinuity_flag,
},
)),
cache_fit_tolerance,
})
}
pub(crate) fn helix_spl_sur(toks: &[Token]) -> Option<DecodedProceduralSurface> {
use cadmpeg_ir::geometry::{
HelixPathConstruction, HelixSurfaceConstruction, HelixSurfaceProfile,
};
let names = ["helix_spl_circ", "helix_spl_line"];
let (start, name) = toks::find_owned_subtype_marker(toks, &names)?;
let circular = name == "helix_spl_circ";
let span = toks::subtype_span(toks, start)?;
let mut cur = Cur::at(span, 2);
let current_layout = optional_helix_revision(&mut cur)?;
let angle_range = [cur.take_range_value()?, cur.take_range_value()?];
let dimension_scale = if circular { LEN_TO_MM } else { 1.0 };
let dimension_range = [
cur.take_range_value()? * dimension_scale,
cur.take_range_value()? * dimension_scale,
];
let length = circular
.then(|| cur.take_f64().map(|v| v * LEN_TO_MM))
.flatten();
let path_angle_range = [cur.take_range_value()?, 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 = normalized(cur.take_vector3()?)?;
for sentinel in ["null_surface", "null_surface", "nullbs", "nullbs"] {
if cur.take_ident()? != sentinel {
return None;
}
}
let path = HelixPathConstruction {
angle_range: path_angle_range,
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,
};
let profile = if let Some(length) = length {
HelixSurfaceProfile::Circle {
length,
radius: cur.take_f64()? * LEN_TO_MM,
}
} else {
let direction = take_frame_vector(&mut cur)?;
HelixSurfaceProfile::Line {
direction: Vector3::new(
direction[0] * LEN_TO_MM,
direction[1] * LEN_TO_MM,
direction[2] * LEN_TO_MM,
),
}
};
Some(DecodedProceduralSurface {
definition: DecodedProceduralSurfaceDefinition::Helix(Box::new(HelixSurfaceConstruction {
angle_range,
dimension_range,
path,
profile,
})),
cache_fit_tolerance: None,
})
}
fn t_spline_subtransform(span: &[Token]) -> Option<cadmpeg_ir::geometry::TSplineSubtransform> {
use cadmpeg_ir::geometry::TSplineSubtransform;
let start = usize::from(matches!(span.first(), Some(Token::SubtypeOpen)));
let mut cur = Cur::at(span, start);
match cur.take_ident()? {
"t_spl_subtrans_object" => {
let program = cur.take_str()?.to_string();
let separator = if matches!(cur.peek(), Some(Token::Str(_))) {
None
} else {
Some(cur.take_bool()?)
};
let values = cur.take_str()?.to_string();
Some(TSplineSubtransform::Inline {
program,
separator,
values,
})
}
"ref" => Some(TSplineSubtransform::Reference {
index: cur.take_long()?,
resolved: None,
}),
_ => None,
}
}
fn resolve_t_spline_subtransform(
index: usize,
table: &SubtypeTable,
seen: &mut Vec<usize>,
) -> Option<cadmpeg_ir::geometry::TSplineSubtransform> {
use cadmpeg_ir::geometry::TSplineSubtransform;
if seen.contains(&index) {
return None;
}
seen.push(index);
let decoded = t_spline_subtransform(table.span(index)?)?;
match decoded {
inline @ TSplineSubtransform::Inline { .. } => Some(inline),
TSplineSubtransform::Reference { index, .. } => {
resolve_t_spline_subtransform(usize::try_from(index).ok()?, table, seen)
}
}
}
pub fn procedural_surface_resolving_refs(
toks: &[Token],
table: &SubtypeTable,
) -> Option<DecodedProceduralSurface> {
procedural_resolving_refs(toks, table, &mut Vec::new())
}
fn procedural_resolving_refs(
toks: &[Token],
table: &SubtypeTable,
seen: &mut Vec<usize>,
) -> Option<DecodedProceduralSurface> {
if let Some(mut decoded) = defm_spl_sur(toks)
.or_else(|| helix_spl_sur(toks))
.or_else(|| t_spl_sur(toks))
.or_else(|| exact_spl_sur(toks))
.or_else(|| comp_spl_sur(toks))
.or_else(|| taper_spl_sur(toks, Some(table)))
.or_else(|| loft_spl_sur(toks, Some(table)))
.or_else(|| compound_loft_spl_sur(toks, Some(table)))
.or_else(|| scaled_compound_loft_spl_sur(toks))
.or_else(|| sub_spl_sur(toks))
.or_else(|| law_spl_sur(toks))
.or_else(|| skin_spl_sur(toks))
.or_else(|| net_spl_sur(toks))
.or_else(|| sweep_spl_sur(toks, Some(table)))
.or_else(|| g2_blend_spl_sur(toks, Some(table)))
.or_else(|| ruled_spl_sur(toks))
.or_else(|| sum_spl_sur(toks, Some(table)))
.or_else(|| rot_spl_sur(toks, Some(table)))
.or_else(|| off_spl_sur(toks, Some(table)))
.or_else(|| cyl_spl_sur(toks, Some(table)))
.or_else(|| var_blend_spl_sur(toks, Some(table)))
.or_else(|| vertex_blend_spl_sur(toks, Some(table)))
.or_else(|| full_rb_blend_spl_sur(toks, table))
.or_else(|| compact_rb_blend_spl_sur(toks))
{
if let DecodedProceduralSurfaceDefinition::TSpline(construction) = &mut decoded.definition {
if let cadmpeg_ir::geometry::TSplineSubtransform::Reference { index, resolved } =
&mut construction.subtransform
{
let inline = resolve_t_spline_subtransform(
usize::try_from(*index).ok()?,
table,
&mut Vec::new(),
)?;
let program = match &inline {
cadmpeg_ir::geometry::TSplineSubtransform::Inline { program, .. } => program,
cadmpeg_ir::geometry::TSplineSubtransform::Reference { .. } => return None,
};
construction.program_graph =
Some(cadmpeg_ir::geometry::TSplineProgram::parse(program));
let values = match &inline {
cadmpeg_ir::geometry::TSplineSubtransform::Inline { values, .. } => values,
cadmpeg_ir::geometry::TSplineSubtransform::Reference { .. } => return None,
};
construction.values_graph =
Some(cadmpeg_ir::geometry::TSplineProgram::parse(values));
*resolved = Some(Box::new(inline));
}
}
return Some(decoded);
}
if toks::owned_subtype_defs(toks)
.iter()
.any(|(_, name)| *name != "ref")
{
return None;
}
for index in toks::subtype_refs(toks) {
if seen.contains(&index) {
continue;
}
let target = table.span(index)?;
seen.push(index);
if let Some(decoded) = procedural_resolving_refs(target, table, seen) {
return Some(decoded);
}
}
None
}
#[cfg(test)]
mod sweep_law_tests {
use super::*;
#[test]
fn sweep_text_law_consumes_one_serializer_token() {
let tokens = [Token::Str("0.008726867790758789*X".into()), Token::Long(21)];
let mut cur = Cur::at(&tokens, 0);
let law = sweep_law_expression(&mut cur).expect("text law");
let EmbeddedLawExpression::Text(value) = law else {
panic!("expected text law");
};
assert_eq!(value, "0.008726867790758789*X");
assert_eq!(cur.take_long(), Some(21));
assert_eq!(cur.pos(), tokens.len());
}
#[test]
fn composition_law_consumes_two_recursive_operands() {
let tokens = [
Token::Str("O".into()),
Token::Str("ABS".into()),
Token::Double(-2.5),
Token::Str("SIN".into()),
Token::Double(0.25),
];
let mut cur = Cur::at(&tokens, 0);
let law = law_expression(&mut cur, 0).expect("composition law");
assert!(matches!(
law,
EmbeddedLawExpression::Algebraic { operator, operands }
if operator == "O"
&& matches!(operands.as_slice(), [
EmbeddedLawExpression::Algebraic { operator: left, operands: left_operands },
EmbeddedLawExpression::Algebraic { operator: right, operands: right_operands },
] if left == "ABS"
&& matches!(left_operands.as_slice(), [EmbeddedLawExpression::Double(value)] if *value == -2.5)
&& right == "SIN"
&& matches!(right_operands.as_slice(), [EmbeddedLawExpression::Double(value)] if *value == 0.25))
));
assert_eq!(cur.pos(), tokens.len());
}
#[test]
fn rail_formula_decodes_counted_vector_transform_binding() {
let vectors = [
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
[2.0, 3.0, 4.0],
];
let mut tokens = vec![
Token::Str("ROTATE(DOMAIN(VEC(1,0,0),0,0.8),TRANS1)".into()),
Token::Long(1),
Token::Str("TRANS".into()),
];
tokens.extend(vectors.into_iter().map(Token::Vector3));
tokens.extend([Token::Double(1.5), Token::True, Token::False, Token::True]);
let mut cur = Cur::at(&tokens, 0);
let formula = law_formula_resolving(&mut cur, None).expect("rail formula");
assert_eq!(formula.name, "ROTATE(DOMAIN(VEC(1,0,0),0,0.8),TRANS1)");
let [EmbeddedLawExpression::TransformVec {
vectors: actual_vectors,
scale,
flags,
}] = formula.variables.as_slice()
else {
panic!("expected one vector transform binding");
};
assert_eq!(
*actual_vectors,
vectors.map(|value| Vector3::new(value[0], value[1], value[2]))
);
assert_eq!(*scale, 1.5);
assert_eq!(*flags, [true, false, true]);
assert_eq!(cur.pos(), tokens.len());
}
}
#[cfg(test)]
mod tail_selector_tests {
use super::*;
fn push_enum(span: &mut Vec<u8>, value: i32) {
span.push(0x15);
span.extend_from_slice(&value.to_le_bytes());
}
fn push_float_array(span: &mut Vec<u8>, count: i32) {
span.push(0x04);
span.extend_from_slice(&count.to_le_bytes());
for _ in 0..count {
span.push(0x06);
span.extend_from_slice(&0.0f64.to_le_bytes());
}
}
#[test]
fn undefined_tail_form_is_rejected_for_verbatim_retention() {
let mut span = Vec::new();
push_enum(&mut span, 1);
span.push(0x06);
span.extend_from_slice(&0.0f64.to_le_bytes());
let toks = toks::lex_test_span(&span, 4);
let mut cur = Cur::at(&toks, 0);
assert!(revision_surface_tail(&mut cur).is_none());
}
#[test]
fn parameterized_tail_form_decodes_intervals_then_closure_enums() {
let mut span = Vec::new();
push_enum(&mut span, 2);
span.push(0x0a);
span.push(0x06);
span.extend_from_slice(&0.25f64.to_le_bytes());
span.push(0x0b);
for value in [(-1.5f64), 3.5] {
span.push(0x0a);
span.push(0x06);
span.extend_from_slice(&value.to_le_bytes());
}
for value in [1, 0, 2, 3] {
push_enum(&mut span, value);
}
push_float_array(&mut span, 1);
for _ in 0..5 {
push_float_array(&mut span, 0);
}
span.push(0x0b);
let toks = toks::lex_test_span(&span, 4);
let mut cur = Cur::at(&toks, 0);
let tail = revision_surface_tail(&mut cur).expect("parameterized tail");
assert_eq!(cur.pos(), toks.len());
assert_eq!(tail.enumeration, 2);
assert_eq!(tail.fit_tolerance, None);
assert_eq!(tail.solved_cache_domains, None);
let parameterization = tail.parameterization.expect("parameterization");
assert_eq!(parameterization.u_interval, [Some(0.25), None]);
assert_eq!(parameterization.v_interval, [Some(-1.5), Some(3.5)]);
assert_eq!(parameterization.u_closure, 1);
assert_eq!(parameterization.v_closure, 0);
assert_eq!(parameterization.u_singularity, 2);
assert_eq!(parameterization.v_singularity, 3);
assert_eq!(tail.discontinuities[0], [0.0]);
assert!(tail.discontinuities[1..].iter().all(Vec::is_empty));
assert!(!tail.tail_flag);
}
}