use super::{decode_pcurve_cache, final_pcurve_patch_layout, pcurve_fit_tolerance};
use crate::kernel_header::RefWidth;
use crate::nurbs::reader::NUBS_MARKER;
fn push_int(out: &mut Vec<u8>, tag: u8, value: i64, int_width: RefWidth) {
out.push(tag);
if int_width == RefWidth::Four {
out.extend_from_slice(
&i32::try_from(value)
.expect("test value fits i32")
.to_le_bytes(),
);
} else {
out.extend_from_slice(&value.to_le_bytes());
}
}
fn push_f64(out: &mut Vec<u8>, value: f64) {
out.push(0x06);
out.extend_from_slice(&value.to_le_bytes());
}
fn push_ident(out: &mut Vec<u8>, value: &str) {
out.extend_from_slice(&[0x0d, u8::try_from(value.len()).expect("test value fits")]);
out.extend_from_slice(value.as_bytes());
}
fn push_string(out: &mut Vec<u8>, value: &str) {
out.extend_from_slice(&[0x07, u8::try_from(value.len()).expect("test value fits")]);
out.extend_from_slice(value.as_bytes());
}
fn push_position(out: &mut Vec<u8>, values: [f64; 3]) {
out.push(0x13);
for value in values {
out.extend_from_slice(&value.to_le_bytes());
}
}
fn push_vector(out: &mut Vec<u8>, values: [f64; 3]) {
out.push(0x14);
for value in values {
out.extend_from_slice(&value.to_le_bytes());
}
}
fn curve_block_with_endpoint(int_width: RefWidth, endpoint: [f64; 3]) -> Vec<u8> {
let mut b = NUBS_MARKER.to_vec();
push_int(&mut b, 0x04, 1, int_width); push_int(&mut b, 0x15, 0, int_width); push_int(&mut b, 0x04, 2, int_width); push_f64(&mut b, 0.0);
push_int(&mut b, 0x04, 1, int_width);
push_f64(&mut b, 1.0);
push_int(&mut b, 0x04, 1, int_width);
for component in [0.0, 0.0, 0.0, endpoint[0], endpoint[1], endpoint[2]] {
push_f64(&mut b, component);
}
b
}
fn curve_block(int_width: RefWidth) -> Vec<u8> {
curve_block_with_endpoint(int_width, [1.0, 2.0, 3.0])
}
fn surface_block_with_x_offset(int_width: RefWidth, x_offset: f64) -> Vec<u8> {
let mut b = NUBS_MARKER.to_vec();
push_int(&mut b, 0x04, 1, int_width); push_int(&mut b, 0x04, 1, int_width); for _ in 0..4 {
push_int(&mut b, 0x15, 0, int_width); }
push_int(&mut b, 0x04, 2, int_width); push_int(&mut b, 0x04, 2, int_width); for _ in 0..2 {
push_f64(&mut b, 0.0);
push_int(&mut b, 0x04, 1, int_width);
push_f64(&mut b, 1.0);
push_int(&mut b, 0x04, 1, int_width);
}
for pole in 0..4 {
push_f64(&mut b, x_offset + f64::from(pole));
push_f64(&mut b, 0.0);
push_f64(&mut b, 0.0);
}
b
}
fn surface_block(int_width: RefWidth) -> Vec<u8> {
surface_block_with_x_offset(int_width, 0.0)
}
fn pcurve_block(int_width: RefWidth) -> Vec<u8> {
let mut b = NUBS_MARKER.to_vec();
push_int(&mut b, 0x04, 1, int_width);
push_int(&mut b, 0x15, 0, int_width);
push_int(&mut b, 0x04, 2, int_width);
for knot in [0.0, 1.0] {
push_f64(&mut b, knot);
push_int(&mut b, 0x04, 1, int_width);
}
for component in [0.0, 0.0, 1.0, 1.0] {
push_f64(&mut b, component);
}
b
}
fn exact_cache_first_curve(int_width: RefWidth) -> Vec<u8> {
let mut bytes = vec![0x0f];
push_ident(&mut bytes, "exact_int_cur");
push_int(&mut bytes, 0x04, 23_100, int_width);
push_int(&mut bytes, 0x15, 0, int_width);
bytes.extend_from_slice(&curve_block(int_width));
push_f64(&mut bytes, 0.0);
push_ident(&mut bytes, "plane");
push_position(&mut bytes, [0.0, 0.0, 0.0]);
push_vector(&mut bytes, [0.0, 0.0, 1.0]);
push_vector(&mut bytes, [1.0, 0.0, 0.0]);
bytes.extend_from_slice(&[0x0b; 5]);
push_ident(&mut bytes, "null_surface");
bytes.extend_from_slice(&pcurve_block(int_width));
push_ident(&mut bytes, "nullbs");
bytes.extend_from_slice(&[0x0b; 2]);
for _ in 0..3 {
push_int(&mut bytes, 0x04, 0, int_width);
}
push_int(&mut bytes, 0x04, 0, int_width);
bytes.push(0x0a);
push_f64(&mut bytes, 1.0);
bytes.push(0x0a);
push_f64(&mut bytes, 0.0);
push_int(&mut bytes, 0x15, 0, int_width);
push_int(&mut bytes, 0x15, 0, int_width);
bytes.push(0x10);
bytes
}
#[test]
fn intcurve_selector_uses_the_serialized_direct_slot() {
let asm_decode_arena = cadmpeg_core::decode::DecodeArena::new();
let (asm_decode_ctx, _) = cadmpeg_core::decode::DecodeContext::from_root_bytes(
&[],
&asm_decode_arena,
&cadmpeg_core::decode::DecodePolicy::default(),
)
.expect("test decode context");
for int_width in [RefWidth::Four, RefWidth::Eight] {
let mut bytes = curve_block(int_width);
bytes.extend_from_slice(&pcurve_block(int_width));
let toks = crate::nurbs::toks::lex_test_span(&bytes, int_width)
.expect("valid single-record byte fixture");
let table = crate::nurbs::toks::test_table(&bytes, int_width)
.expect("valid single-record byte fixture");
assert!(crate::nurbs::proc_curve::pcurve_for_selector_with_chart(
&asm_decode_ctx,
&toks,
2,
&table
)
.transpose()
.expect("decode the selected pcurve with its chart")
.is_some());
assert!(crate::nurbs::proc_curve::pcurve_for_selector_with_chart(
&asm_decode_ctx,
&toks,
-2,
&table
)
.transpose()
.expect("decode the selected pcurve with its chart")
.is_some());
assert!(crate::nurbs::proc_curve::pcurve_for_selector_with_chart(
&asm_decode_ctx,
&toks,
1,
&table
)
.transpose()
.expect("decode the selected pcurve with its chart")
.is_none());
}
}
#[test]
fn exact_curve_selector_uses_its_cache_first_support_slot() {
let asm_decode_arena = cadmpeg_core::decode::DecodeArena::new();
let (asm_decode_ctx, _) = cadmpeg_core::decode::DecodeContext::from_root_bytes(
&[],
&asm_decode_arena,
&cadmpeg_core::decode::DecodePolicy::default(),
)
.expect("test decode context");
for int_width in [RefWidth::Four, RefWidth::Eight] {
let bytes = exact_cache_first_curve(int_width);
let toks = crate::nurbs::toks::lex_test_span(&bytes, int_width)
.expect("valid single-record byte fixture");
let table = crate::nurbs::toks::test_table(&bytes, int_width)
.expect("valid single-record byte fixture");
let decoded = crate::nurbs::proc_curve::procedural_curve_resolving_refs(
&asm_decode_ctx,
&toks,
&table,
)
.transpose()
.expect("decode the procedural curve")
.unwrap_or_else(|| panic!("exact curve at width {int_width}"));
assert!(matches!(
decoded.construction,
crate::nurbs::proc_curve::ProceduralCurveConstruction::Exact
));
let (pcurve, _) = crate::nurbs::proc_curve::pcurve_for_selector_with_chart(
&asm_decode_ctx,
&toks,
1,
&table,
)
.transpose()
.expect("decode the selected pcurve with its chart")
.unwrap_or_else(|| panic!("exact curve pcurve at width {int_width}"));
assert_eq!(
pcurve.control_points()[1],
cadmpeg_ir::math::Point2::new(10.0, 10.0)
);
assert!(crate::nurbs::proc_curve::pcurve_for_selector_with_chart(
&asm_decode_ctx,
&toks,
-1,
&table
)
.transpose()
.expect("decode the selected pcurve with its chart")
.is_some());
assert!(crate::nurbs::proc_curve::pcurve_for_selector_with_chart(
&asm_decode_ctx,
&toks,
2,
&table
)
.transpose()
.expect("decode the selected pcurve with its chart")
.is_none());
}
}
#[test]
fn exact_curve_selector_follows_subtype_reference() {
let asm_decode_arena = cadmpeg_core::decode::DecodeArena::new();
let (asm_decode_ctx, _) = cadmpeg_core::decode::DecodeContext::from_root_bytes(
&[],
&asm_decode_arena,
&cadmpeg_core::decode::DecodePolicy::default(),
)
.expect("test decode context");
for int_width in [RefWidth::Four, RefWidth::Eight] {
let active = exact_cache_first_curve(int_width);
for named in [false, true] {
let mut wrapper = vec![0x0f];
if named {
push_ident(&mut wrapper, "ref");
}
push_int(&mut wrapper, 0x04, 0, int_width);
wrapper.push(0x10);
let toks = crate::nurbs::toks::lex_test_span(&wrapper, int_width)
.expect("valid single-record byte fixture");
let table = crate::nurbs::toks::test_table(&active, int_width)
.expect("valid single-record byte fixture");
assert!(crate::nurbs::proc_curve::pcurve_for_selector_with_chart(
&asm_decode_ctx,
&toks,
-1,
&table
)
.transpose()
.expect("decode the selected pcurve with its chart")
.is_some());
assert!(crate::nurbs::proc_curve::pcurve_for_selector_with_chart(
&asm_decode_ctx,
&toks,
2,
&table
)
.transpose()
.expect("decode the selected pcurve with its chart")
.is_none());
}
}
}
fn rolling_ball_side(int_width: RefWidth, label: &str) -> Vec<u8> {
let mut bytes = Vec::new();
push_string(
&mut bytes,
if label == "left" {
"blend_support_surface"
} else {
"blend_support_curve"
},
);
push_ident(&mut bytes, "null_surface");
bytes.extend_from_slice(&curve_block(int_width));
bytes.extend_from_slice(&[0x0b, 0x0b]);
bytes.extend_from_slice(&pcurve_block(int_width));
push_position(&mut bytes, [7.0, 8.0, 9.0]);
push_ident(&mut bytes, "nullbs");
push_int(&mut bytes, 0x04, 0, int_width);
push_ident(&mut bytes, "nullbs");
bytes
}
fn variable_blend_side(int_width: RefWidth, name: &str, extension: Option<i64>) -> Vec<u8> {
let mut bytes = Vec::new();
push_string(&mut bytes, name);
push_ident(&mut bytes, "null_surface");
push_ident(&mut bytes, "null_curve");
bytes.extend_from_slice(&pcurve_block(int_width));
push_position(&mut bytes, [1.0, 2.0, 3.0]);
push_ident(&mut bytes, "nullbs");
if let Some(extension) = extension {
push_int(&mut bytes, 0x04, extension, int_width);
push_ident(&mut bytes, "nullbs");
}
bytes.push(0x10);
bytes
}
mod blend_laws;
mod cache_selection;
mod procedural_curves;
mod procedural_surfaces;
mod side_charts;