1use std::collections::{BTreeMap, BTreeSet};
5
6use cadmpeg_ir::be;
7use cadmpeg_ir::math::Point3;
8use serde::{Deserialize, Serialize};
9
10use crate::topology::{self, CompositeCurve};
11
12const MISSING_PARAMETER: f64 = -31_415_800_000_000.0;
13const INLINE_TERM_TAIL: &[u8] = b"\x00\x00\x00\x01\x01\x63\x43\x5a";
14const INLINE_UV_TAIL: &[u8] = b"\x00\x00\x00\x02\x01\x66\x01";
15pub type SupportUv = [Option<Vec<[f64; 2]>>; 2];
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
20#[serde(rename_all = "snake_case")]
21pub enum ChartFraming {
22 Direct,
24 Escaped,
26}
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
30#[serde(rename_all = "snake_case")]
31pub enum ChartPointLayout {
32 Xyz3,
34 Ext11,
36}
37
38#[derive(Debug, Clone, PartialEq)]
40pub struct ChartSourceRecord {
41 pub xmt: u32,
43 pub count: u32,
45 pub base_parameter: f64,
47 pub base_scale: f64,
49 pub chart_count: u32,
51 pub chordal_error: f64,
53 pub angular_error: f64,
55 pub parameter_errors: [f64; 2],
57 pub points: Vec<Point3>,
59 pub native_parameters: Option<Vec<f64>>,
61 pub ext_support_uv: SupportUv,
63 pub point_layout: ChartPointLayout,
65 pub framing: ChartFraming,
67 pub pos: usize,
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq)]
73pub struct BlendBound {
74 pub xmt: u32,
76 pub header_references: [u32; 5],
78 pub sense: bool,
80 pub boundary_index: u32,
82 pub blend_surface: u32,
84 pub escaped: bool,
86 pub pos: usize,
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
92#[serde(rename_all = "snake_case")]
93pub enum TermUseFraming {
94 Direct,
96 Escaped,
98 DescriptorInline,
100}
101
102#[derive(Debug, Clone, Copy, PartialEq)]
104pub struct TermUse {
105 pub xmt: u32,
107 pub count: u32,
109 pub form: [u8; 2],
111 pub point: Point3,
113 pub framing: TermUseFraming,
115 pub pos: usize,
117}
118
119#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
121#[serde(rename_all = "snake_case")]
122pub enum SupportUvFraming {
123 Direct,
125 Escaped,
127 DescriptorInline,
129}
130
131#[derive(Debug, Clone, PartialEq)]
133pub struct SupportUvRecord {
134 pub xmt: u32,
136 pub count: u32,
138 pub marker: u8,
140 pub values: Vec<f64>,
142 pub framing: SupportUvFraming,
144 pub pos: usize,
146}
147
148impl SupportUvRecord {
149 fn support_uv(&self) -> SupportUv {
150 let width = if self.marker == 4 { 4 } else { 2 };
151 let first = self
152 .values
153 .chunks_exact(width)
154 .map(|entry| [entry[0], entry[1]])
155 .collect();
156 let second = (self.marker == 4).then(|| {
157 self.values
158 .chunks_exact(4)
159 .map(|entry| [entry[2], entry[3]])
160 .collect()
161 });
162 [Some(first), second]
163 }
164}
165
166#[derive(Debug, Clone)]
168pub struct IntersectionCurve {
169 pub xmt: u32,
171 pub references: [u32; 6],
173 pub supports: [u32; 2],
175 pub pos: usize,
177 pub points: Vec<Point3>,
179 pub parameters: Vec<f64>,
181 pub fit_tolerance: f64,
183 pub support_uv: SupportUv,
185 pub ext_support_uv: SupportUv,
187}
188
189#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
192pub struct RejectionCounts {
193 pub missing_chart: usize,
195 pub missing_start_term: usize,
197 pub missing_end_term: usize,
199 pub endpoint_mismatch: usize,
201}
202
203impl RejectionCounts {
204 pub fn total(self) -> usize {
206 self.missing_chart
207 + self.missing_start_term
208 + self.missing_end_term
209 + self.endpoint_mismatch
210 }
211
212 fn add(&mut self, rejection: Rejection) {
213 match rejection {
214 Rejection::MissingChart => self.missing_chart += 1,
215 Rejection::MissingStartTerm => self.missing_start_term += 1,
216 Rejection::MissingEndTerm => self.missing_end_term += 1,
217 Rejection::EndpointMismatch => self.endpoint_mismatch += 1,
218 }
219 }
220
221 pub fn extend(&mut self, other: Self) {
223 self.missing_chart += other.missing_chart;
224 self.missing_start_term += other.missing_start_term;
225 self.missing_end_term += other.missing_end_term;
226 self.endpoint_mismatch += other.endpoint_mismatch;
227 }
228}
229
230#[derive(Debug, Clone, Default)]
232pub struct CurveScan {
233 pub constructions: Vec<CompositeCurve>,
236 pub curves: Vec<IntersectionCurve>,
238 pub rejected: RejectionCounts,
240}
241
242#[derive(Debug, Clone, Copy)]
243enum Rejection {
244 MissingChart,
245 MissingStartTerm,
246 MissingEndTerm,
247 EndpointMismatch,
248}
249
250#[derive(Debug, Clone)]
251struct Chart {
252 points: Vec<Point3>,
253 parameters: Vec<f64>,
254 fit_tolerance: f64,
255 ext_support_uv: SupportUv,
256}
257
258#[derive(Debug, Clone)]
259struct ChartPoints {
260 points: Vec<Point3>,
261 native_parameters: Option<Vec<f64>>,
262 ext_support_uv: SupportUv,
263}
264
265pub fn curves(stream: &[u8]) -> Vec<IntersectionCurve> {
268 scan(stream).curves
269}
270
271pub fn scan(stream: &[u8]) -> CurveScan {
273 scan_with_auxiliaries(stream, &chart_records(stream), &term_records(stream))
274}
275
276pub(crate) fn scan_with_auxiliary_replacements(
278 stream: &[u8],
279 base_stream: &[u8],
280 replacement_streams: &[&[u8]],
281) -> CurveScan {
282 let mut charts = chart_records(base_stream);
283 let mut terms = term_records(base_stream);
284 for replacement_stream in replacement_streams {
285 charts.extend(chart_records(replacement_stream));
286 terms.extend(term_records(replacement_stream));
287 }
288 scan_with_auxiliaries(stream, &charts, &terms)
289}
290
291fn scan_with_auxiliaries(
292 stream: &[u8],
293 charts: &BTreeMap<u32, Chart>,
294 terms: &BTreeMap<u32, Point3>,
295) -> CurveScan {
296 let uv = uv_records(stream);
297 let bridges = blend_bound_records(stream);
298 let graph = topology::Graph::parse(stream);
299 let referenced_curves = graph.referenced_curve_xmts();
300 let mut result = CurveScan::default();
301 for construction in topology::composite_curves(stream)
302 .into_iter()
303 .chain(topology::intersection_data_curves(stream))
304 {
305 match enrich(construction, charts, terms, &uv, &bridges, &graph) {
306 Ok(curve) => {
307 result.constructions.push(construction);
308 result.curves.push(curve);
309 }
310 Err(rejection) if referenced_curves.contains(&construction.xmt) => {
311 result.constructions.push(construction);
312 result.rejected.add(rejection);
313 }
314 Err(_) => {}
315 }
316 }
317 result
318}
319
320fn enrich(
321 construction: CompositeCurve,
322 charts: &BTreeMap<u32, Chart>,
323 terms: &BTreeMap<u32, Point3>,
324 uv: &BTreeMap<u32, SupportUv>,
325 bridges: &BTreeMap<u32, u32>,
326 graph: &topology::Graph,
327) -> Result<IntersectionCurve, Rejection> {
328 let chart = charts
329 .get(&construction.references[2])
330 .ok_or(Rejection::MissingChart)?;
331 let chart_endpoints = [
332 *chart.points.first().ok_or(Rejection::MissingChart)?,
333 *chart.points.last().ok_or(Rejection::MissingChart)?,
334 ];
335 let serialized_terms = [
336 terms.get(&construction.references[3]).copied(),
337 terms.get(&construction.references[4]).copied(),
338 ];
339 if serialized_terms
340 .iter()
341 .zip(chart_endpoints)
342 .any(|(term, endpoint)| {
343 term.is_some_and(|term| distance(term, endpoint) > chart.fit_tolerance)
344 })
345 {
346 return Err(Rejection::EndpointMismatch);
347 }
348 if serialized_terms.iter().any(Option::is_none) {
349 let topology_endpoints = graph
350 .unique_curve_edge_endpoints(construction.xmt)
351 .ok_or_else(|| {
352 if serialized_terms[0].is_none() {
353 Rejection::MissingStartTerm
354 } else {
355 Rejection::MissingEndTerm
356 }
357 })?;
358 let matching_permutations = [[0usize, 1usize], [1usize, 0usize]]
359 .into_iter()
360 .filter(|permutation| {
361 permutation.iter().enumerate().all(|(ordinal, topology)| {
362 distance(chart_endpoints[ordinal], topology_endpoints[*topology])
363 <= chart.fit_tolerance
364 })
365 })
366 .count();
367 if matching_permutations != 1 {
368 return Err(if serialized_terms[0].is_none() {
369 Rejection::MissingStartTerm
370 } else {
371 Rejection::MissingEndTerm
372 });
373 }
374 }
375 let support_uv = uv
376 .get(&construction.references[5])
377 .cloned()
378 .unwrap_or([None, None]);
379 let first_is_surface = is_surface(graph, construction.references[0]);
380 let second_is_surface = is_surface(graph, construction.references[1]);
381 let (primary, bridge) = if first_is_surface {
382 (construction.references[0], construction.references[1])
383 } else if second_is_surface {
384 (construction.references[1], construction.references[0])
385 } else {
386 (1, 1)
387 };
388 let secondary = bridges
389 .get(&bridge)
390 .copied()
391 .or_else(|| is_surface(graph, bridge).then_some(bridge))
392 .filter(|secondary| *secondary != primary)
393 .unwrap_or(1);
394 Ok(IntersectionCurve {
395 xmt: construction.xmt,
396 references: construction.references,
397 supports: [primary, secondary],
398 pos: construction.pos,
399 points: chart.points.clone(),
400 parameters: chart.parameters.clone(),
401 fit_tolerance: chart.fit_tolerance,
402 support_uv,
403 ext_support_uv: chart.ext_support_uv.clone(),
404 })
405}
406
407fn blend_bound_records(stream: &[u8]) -> BTreeMap<u32, u32> {
408 blend_bounds(stream)
409 .into_iter()
410 .map(|bound| (bound.xmt, bound.blend_surface))
411 .collect()
412}
413
414pub fn blend_bounds(stream: &[u8]) -> Vec<BlendBound> {
416 let mut out = BTreeMap::new();
417 let mut duplicates = BTreeSet::new();
418 for tag in find_tags(stream, [0, 59]) {
419 for escape in [0usize, 1] {
420 if escape == 1 && stream.get(tag + 2) != Some(&0xff) {
421 continue;
422 }
423 let mut at = tag + 2 + escape;
424 let Some((xmt, consumed)) = read_xmt(stream, at) else {
425 continue;
426 };
427 at += consumed + 4;
428 let mut header = [0u32; 5];
429 let mut valid = true;
430 for reference in &mut header {
431 let Some((value, consumed)) = read_xmt(stream, at) else {
432 valid = false;
433 break;
434 };
435 *reference = value;
436 at += consumed;
437 }
438 if !valid || header[0] != 1 {
439 continue;
440 }
441 let sense = match stream.get(at) {
442 Some(b'+') => true,
443 Some(b'-') => false,
444 _ => continue,
445 };
446 at += 1;
447 let Some((boundary, consumed)) = read_xmt(stream, at) else {
448 continue;
449 };
450 let Some((surface, _)) = read_xmt(stream, at + consumed) else {
451 continue;
452 };
453 if boundary <= 1 && surface > 1 {
454 insert_unique(
455 &mut out,
456 &mut duplicates,
457 xmt,
458 BlendBound {
459 xmt,
460 header_references: header,
461 sense,
462 boundary_index: boundary,
463 blend_surface: surface,
464 escaped: escape == 1,
465 pos: tag,
466 },
467 );
468 break;
469 }
470 }
471 }
472 out.into_values().collect()
473}
474
475fn is_surface(graph: &topology::Graph, xmt: u32) -> bool {
476 [50, 51, 52, 53, 54, 56, 60, 124]
477 .into_iter()
478 .any(|kind| graph.get(kind, xmt).is_some())
479}
480
481fn chart_records(stream: &[u8]) -> BTreeMap<u32, Chart> {
482 let mut out = BTreeMap::new();
483 let mut complemented = BTreeSet::new();
484 let mut duplicates = BTreeSet::new();
485 for source in chart_source_records(stream) {
486 if duplicates.contains(&source.xmt) {
487 continue;
488 }
489 let fit_tolerance = source.chordal_error * 1000.0;
490 if !fit_tolerance.is_finite() {
491 continue;
492 }
493 let mut chord_parameters = Vec::with_capacity(source.points.len());
494 chord_parameters.push(source.base_parameter);
495 for pair in source.points.windows(2) {
496 let chord_m = distance(pair[0], pair[1]) / 1000.0;
497 chord_parameters.push(
498 chord_parameters
499 .last()
500 .copied()
501 .expect("invariant: base parameter inserted")
502 + chord_m * source.base_scale,
503 );
504 }
505 let candidate = Chart {
506 points: source.points,
507 parameters: source.native_parameters.clone().unwrap_or(chord_parameters),
508 fit_tolerance,
509 ext_support_uv: source.ext_support_uv,
510 };
511 match out.entry(source.xmt) {
512 std::collections::btree_map::Entry::Vacant(entry) => {
513 entry.insert(candidate);
514 }
515 std::collections::btree_map::Entry::Occupied(mut entry)
516 if !complemented.contains(&source.xmt)
517 && source.native_parameters.is_some()
518 && entry.get().points.len() == candidate.points.len()
519 && entry.get().points.iter().zip(&candidate.points).all(
520 |(first, second)| {
521 distance(*first, *second)
522 <= entry.get().fit_tolerance.max(candidate.fit_tolerance)
523 },
524 ) =>
525 {
526 entry.get_mut().parameters = candidate.parameters;
527 entry.get_mut().ext_support_uv = candidate.ext_support_uv;
528 complemented.insert(source.xmt);
529 }
530 std::collections::btree_map::Entry::Occupied(entry) => {
531 entry.remove();
532 duplicates.insert(source.xmt);
533 }
534 }
535 }
536 out
537}
538
539pub fn chart_source_records(stream: &[u8]) -> Vec<ChartSourceRecord> {
541 let mut out = Vec::new();
542 for tag in find_tags(stream, [0, 40]) {
543 for escape in [0usize, 1] {
544 if escape == 1 && stream.get(tag + 2) != Some(&0xff) {
545 continue;
546 }
547 let base = tag + 2 + escape;
548 let Some(count) = be::u32_at(stream, base).map(|value| value as usize) else {
549 continue;
550 };
551 if !(2..=1024).contains(&count) {
552 continue;
553 }
554 let Some((xmt, xmt_len)) = read_xmt(stream, base + 4) else {
555 continue;
556 };
557 let preamble = base + 4 + xmt_len;
558 let Some(base_parameter) = be::f64_at(stream, preamble) else {
559 continue;
560 };
561 let Some(base_scale) = be::f64_at(stream, preamble + 8) else {
562 continue;
563 };
564 let Some(chart_count) = be::u32_at(stream, preamble + 16) else {
565 continue;
566 };
567 let Some(chordal_error) = be::f64_at(stream, preamble + 20) else {
568 continue;
569 };
570 let Some(angular_error) = be::f64_at(stream, preamble + 28) else {
571 continue;
572 };
573 let errors = [
574 be::f64_at(stream, preamble + 36),
575 be::f64_at(stream, preamble + 44),
576 ];
577 if chart_count as usize != count
578 || !base_parameter.is_finite()
579 || !base_scale.is_finite()
580 || base_scale == 0.0
581 || !chordal_error.is_finite()
582 || chordal_error <= 0.0
583 || !angular_error.is_finite()
584 || errors != [Some(MISSING_PARAMETER), Some(MISSING_PARAMETER)]
585 {
586 continue;
587 }
588 let block = preamble + 52;
589 let Some(chart_points) = chart_points(stream, block, count) else {
590 continue;
591 };
592 let point_layout = if chart_points.native_parameters.is_some() {
593 ChartPointLayout::Ext11
594 } else {
595 ChartPointLayout::Xyz3
596 };
597 out.push(ChartSourceRecord {
598 xmt,
599 count: count as u32,
600 base_parameter,
601 base_scale,
602 chart_count,
603 chordal_error,
604 angular_error,
605 parameter_errors: [
606 errors[0].expect("validated parameter error"),
607 errors[1].expect("validated parameter error"),
608 ],
609 points: chart_points.points,
610 native_parameters: chart_points.native_parameters,
611 ext_support_uv: chart_points.ext_support_uv,
612 point_layout,
613 framing: if escape == 0 {
614 ChartFraming::Direct
615 } else {
616 ChartFraming::Escaped
617 },
618 pos: tag,
619 });
620 break;
621 }
622 }
623 out
624}
625
626fn chart_points(stream: &[u8], block: usize, count: usize) -> Option<ChartPoints> {
627 let ext = (0..count)
628 .map(|index| {
629 let at = block + index * 88;
630 let point = point_m(stream, at)?;
631 let tangent = [
632 be::f64_at(stream, at + 56)?,
633 be::f64_at(stream, at + 64)?,
634 be::f64_at(stream, at + 72)?,
635 ];
636 let norm = tangent.iter().map(|v| v * v).sum::<f64>().sqrt();
637 let parameter = be::f64_at(stream, at + 80)?;
638 let parameter_lanes = [
639 [be::f64_at(stream, at + 24)?, be::f64_at(stream, at + 40)?],
640 [be::f64_at(stream, at + 32)?, be::f64_at(stream, at + 48)?],
641 ];
642 ((norm - 1.0).abs() < 1.0e-9 && parameter.is_finite()).then_some((
643 point,
644 parameter,
645 parameter_lanes,
646 ))
647 })
648 .collect::<Option<Vec<_>>>();
649 if let Some(entries) = ext {
650 let mut points = Vec::with_capacity(entries.len());
651 let mut native_parameters = Vec::with_capacity(entries.len());
652 let mut ext_support_uv = [Some(Vec::new()), Some(Vec::new())];
653 for (point, parameter, lanes) in entries {
654 points.push(point);
655 native_parameters.push(parameter);
656 for lane in 0..2 {
657 if lanes[lane]
658 .iter()
659 .all(|value| value.is_finite() && *value != MISSING_PARAMETER)
660 {
661 if let Some(values) = &mut ext_support_uv[lane] {
662 values.push(lanes[lane]);
663 }
664 } else {
665 ext_support_uv[lane] = None;
666 }
667 }
668 }
669 if native_parameters.windows(2).all(|pair| pair[0] < pair[1]) {
670 return Some(ChartPoints {
671 points,
672 native_parameters: Some(native_parameters),
673 ext_support_uv,
674 });
675 }
676 }
677 let points = (0..count)
678 .map(|index| point_m(stream, block + index * 24))
679 .collect::<Option<Vec<_>>>()?;
680 (points.windows(2).any(|pair| pair[0] != pair[1])).then_some(ChartPoints {
681 points,
682 native_parameters: None,
683 ext_support_uv: [None, None],
684 })
685}
686
687fn term_records(stream: &[u8]) -> BTreeMap<u32, Point3> {
688 term_use_records(stream)
689 .into_iter()
690 .map(|term| (term.xmt, term.point))
691 .collect()
692}
693
694pub fn term_use_records(stream: &[u8]) -> Vec<TermUse> {
696 let mut out = BTreeMap::new();
697 let mut duplicates = BTreeSet::new();
698 for tag in find_tags(stream, [0, 41]) {
699 for escape in [0usize, 1] {
700 if escape == 1 && stream.get(tag + 2) != Some(&0xff) {
701 continue;
702 }
703 let base = tag + 2 + escape;
704 let framing = if escape == 0 {
705 TermUseFraming::Direct
706 } else {
707 TermUseFraming::Escaped
708 };
709 if let Some(term) = term_at(stream, base, framing, tag) {
710 insert_unique(&mut out, &mut duplicates, term.xmt, term);
711 break;
712 }
713 }
714 }
715 for label in find_bytes(stream, b"term_use") {
716 let tail = label + b"term_use".len();
717 if stream.get(tail..tail + INLINE_TERM_TAIL.len()) == Some(INLINE_TERM_TAIL) {
718 let pos = tail + INLINE_TERM_TAIL.len();
719 if let Some(term) = term_at(stream, pos, TermUseFraming::DescriptorInline, pos) {
720 insert_unique(&mut out, &mut duplicates, term.xmt, term);
721 }
722 }
723 }
724 out.into_values().collect()
725}
726
727fn term_at(stream: &[u8], base: usize, framing: TermUseFraming, pos: usize) -> Option<TermUse> {
728 let count = be::u32_at(stream, base)?;
729 let (xmt, xmt_len) = read_xmt(stream, base + 4)?;
730 let payload = base + 4 + xmt_len;
731 let form: [u8; 2] = stream.get(payload..payload + 2)?.try_into().ok()?;
732 let valid = (count == 1 && form == *b"L?") || (count == 2 && matches!(&form, b"TF" | b"TS"));
733 valid.then_some(())?;
734 Some(TermUse {
735 xmt,
736 count,
737 form,
738 point: point_m(stream, payload + 2)?,
739 framing,
740 pos,
741 })
742}
743
744fn uv_records(stream: &[u8]) -> BTreeMap<u32, SupportUv> {
745 support_uv_records(stream)
746 .into_iter()
747 .map(|record| (record.xmt, record.support_uv()))
748 .collect()
749}
750
751pub fn support_uv_records(stream: &[u8]) -> Vec<SupportUvRecord> {
753 let mut out = BTreeMap::new();
754 let mut duplicates = BTreeSet::new();
755 for tag in find_tags(stream, [0, 204]) {
756 for escape in [0usize, 1] {
757 if escape == 1 && stream.get(tag + 2) != Some(&0xff) {
758 continue;
759 }
760 let base = tag + 2 + escape;
761 let framing = if escape == 0 {
762 SupportUvFraming::Direct
763 } else {
764 SupportUvFraming::Escaped
765 };
766 if let Some(record) = uv_at(stream, base, framing, tag) {
767 insert_unique(&mut out, &mut duplicates, record.xmt, record);
768 break;
769 }
770 }
771 }
772 for label in find_bytes(stream, b"values") {
773 let tail = label + b"values".len();
774 if stream.get(tail..tail + INLINE_UV_TAIL.len()) == Some(INLINE_UV_TAIL) {
775 let pos = tail + INLINE_UV_TAIL.len();
776 if let Some(record) = uv_at(stream, pos, SupportUvFraming::DescriptorInline, pos) {
777 insert_unique(&mut out, &mut duplicates, record.xmt, record);
778 }
779 }
780 }
781 out.into_values().collect()
782}
783
784fn insert_unique<T>(
785 records: &mut BTreeMap<u32, T>,
786 duplicates: &mut BTreeSet<u32>,
787 xmt: u32,
788 record: T,
789) {
790 if duplicates.contains(&xmt) {
791 return;
792 }
793 if records.insert(xmt, record).is_some() {
794 records.remove(&xmt);
795 duplicates.insert(xmt);
796 }
797}
798
799fn uv_at(
800 stream: &[u8],
801 base: usize,
802 framing: SupportUvFraming,
803 pos: usize,
804) -> Option<SupportUvRecord> {
805 let count = be::u32_at(stream, base)?;
806 let count_usize = count as usize;
807 let (xmt, xmt_len) = read_xmt(stream, base + 4)?;
808 let payload = base + 4 + xmt_len;
809 let marker @ 2..=4 = stream.get(payload).copied()? else {
810 return None;
811 };
812 let width = if marker == 4 { 4 } else { 2 };
813 if count_usize < width * 2 || !count_usize.is_multiple_of(width) {
814 return None;
815 }
816 let values = (0..count_usize)
817 .map(|index| be::f64_at(stream, payload + 1 + index * 8))
818 .collect::<Option<Vec<_>>>()?;
819 if !values.iter().all(|value| value.is_finite()) {
820 return None;
821 }
822 Some(SupportUvRecord {
823 xmt,
824 count,
825 marker,
826 values,
827 framing,
828 pos,
829 })
830}
831
832fn find_tags(stream: &[u8], tag: [u8; 2]) -> impl Iterator<Item = usize> + '_ {
833 stream
834 .windows(2)
835 .enumerate()
836 .filter_map(move |(offset, bytes)| (bytes == tag).then_some(offset))
837}
838
839fn find_bytes<'a>(stream: &'a [u8], needle: &'a [u8]) -> impl Iterator<Item = usize> + 'a {
840 stream
841 .windows(needle.len())
842 .enumerate()
843 .filter_map(move |(offset, bytes)| (bytes == needle).then_some(offset))
844}
845
846fn point_m(stream: &[u8], at: usize) -> Option<Point3> {
847 let xyz = be::vec3_at(stream, at)?;
848 xyz.iter()
849 .all(|value| value.is_finite() && value.abs() < 100.0)
850 .then_some(Point3::new(
851 xyz[0] * 1000.0,
852 xyz[1] * 1000.0,
853 xyz[2] * 1000.0,
854 ))
855}
856
857fn distance(first: Point3, second: Point3) -> f64 {
858 ((first.x - second.x).powi(2) + (first.y - second.y).powi(2) + (first.z - second.z).powi(2))
859 .sqrt()
860}
861
862fn read_xmt(stream: &[u8], at: usize) -> Option<(u32, usize)> {
863 let first = i16::from_be_bytes([*stream.get(at)?, *stream.get(at + 1)?]);
864 if first >= 0 {
865 return Some((first as u32, 2));
866 }
867 let remainder = first.unsigned_abs();
868 let quotient = u16::from_be_bytes([*stream.get(at + 2)?, *stream.get(at + 3)?]);
869 Some((u32::from(quotient) * 32_767 + u32::from(remainder), 4))
870}