1#![deny(clippy::disallowed_methods)]
15
16use cadmpeg_ir::be::{f64_at as read_f64, vec3_at as read_vec3};
17use cadmpeg_ir::geometry::{CurveGeometry, SurfaceGeometry};
18use cadmpeg_ir::math::{Point3, Vector3};
19
20const SHIFTS: [usize; 6] = [0, 2, 4, 6, 8, 10];
23
24#[derive(Debug, Clone)]
26pub struct DecodedSurface {
27 pub pos: usize,
29 pub geometry: SurfaceGeometry,
31}
32
33#[derive(Debug, Clone)]
35pub struct DecodedCurve {
36 pub pos: usize,
38 pub geometry: CurveGeometry,
40}
41
42#[derive(Debug, Clone)]
44pub struct DecodedPoint {
45 pub pos: usize,
47 pub position: Point3,
49}
50
51const SURFACE_TAGS: [(u8, usize); 5] = [
53 (0x32, 91), (0x33, 99), (0x34, 115), (0x35, 99), (0x36, 107), ];
59
60const CURVE_TAGS: [(u8, usize); 3] = [
62 (0x1e, 67), (0x1f, 99), (0x20, 107), ];
66
67enum AnalyticRecord {
68 Point(DecodedPoint),
69 Surface(DecodedSurface),
70 Curve(DecodedCurve),
71}
72
73#[derive(Clone, Copy)]
74enum DecodeContext {
75 Scanner,
76 Graph,
77}
78
79pub fn points(stream: &[u8]) -> Vec<DecodedPoint> {
84 analytic_records(stream)
85 .into_iter()
86 .filter_map(|record| match record {
87 AnalyticRecord::Point(point) => Some(point),
88 AnalyticRecord::Surface(_) | AnalyticRecord::Curve(_) => None,
89 })
90 .collect()
91}
92
93pub fn surfaces(stream: &[u8]) -> Vec<DecodedSurface> {
95 analytic_records(stream)
96 .into_iter()
97 .filter_map(|record| match record {
98 AnalyticRecord::Surface(surface) => Some(surface),
99 AnalyticRecord::Point(_) | AnalyticRecord::Curve(_) => None,
100 })
101 .collect()
102}
103
104pub fn curves(stream: &[u8]) -> Vec<DecodedCurve> {
106 analytic_records(stream)
107 .into_iter()
108 .filter_map(|record| match record {
109 AnalyticRecord::Curve(curve) => Some(curve),
110 AnalyticRecord::Point(_) | AnalyticRecord::Surface(_) => None,
111 })
112 .collect()
113}
114
115fn analytic_records(stream: &[u8]) -> Vec<AnalyticRecord> {
116 let mut out = Vec::new();
117 let mut p = 0usize;
118 while p + 2 <= stream.len() {
119 if stream[p] != 0x00 {
120 p += 1;
121 continue;
122 }
123 let kind = stream[p + 1];
124 let candidate = if kind == 0x1d {
125 decode_point(stream, p)
126 .map(|(point, shift)| (AnalyticRecord::Point(point), p + 40 + shift))
127 } else if let Some((_, len)) = SURFACE_TAGS.iter().find(|(tag, _)| *tag == kind) {
128 decode_surface(stream, p, kind).map(|(geometry, shift)| {
129 (
130 AnalyticRecord::Surface(DecodedSurface { pos: p, geometry }),
131 p + *len + shift,
132 )
133 })
134 } else if let Some((_, len)) = CURVE_TAGS.iter().find(|(tag, _)| *tag == kind) {
135 decode_curve(stream, p, kind).map(|(geometry, shift)| {
136 (
137 AnalyticRecord::Curve(DecodedCurve { pos: p, geometry }),
138 p + *len + shift,
139 )
140 })
141 } else {
142 None
143 };
144 if let Some((record, end)) = candidate {
145 out.push(record);
146 p = end;
147 } else {
148 p += 1;
149 }
150 }
151 out
152}
153
154fn decode_point(stream: &[u8], p: usize) -> Option<(DecodedPoint, usize)> {
155 SHIFTS.into_iter().find_map(|shift| {
156 let xyz = read_vec3(stream, p + 16 + shift)?;
157 (xyz.iter().all(|value| {
158 value.is_finite() && value.abs() < 1.0e3 && (*value == 0.0 || value.abs() >= 1.0e-100)
159 }) && xyz.iter().any(|value| *value != 0.0))
160 .then_some((
161 DecodedPoint {
162 pos: p,
163 position: mm_point(xyz),
164 },
165 shift,
166 ))
167 })
168}
169
170fn decode_surface(stream: &[u8], p: usize, kind: u8) -> Option<(SurfaceGeometry, usize)> {
173 for sh in SHIFTS {
174 let b = p + sh;
175 let geom = match kind {
176 0x32 => plane(stream, b, DecodeContext::Scanner),
177 0x33 => cylinder(stream, b, DecodeContext::Scanner),
178 0x34 => cone(stream, b, DecodeContext::Scanner),
179 0x35 => sphere(stream, b, DecodeContext::Scanner),
180 0x36 => torus(stream, b, DecodeContext::Scanner),
181 _ => None,
182 };
183 if let Some(geometry) = geom {
184 return Some((geometry, sh));
185 }
186 }
187 None
188}
189
190fn decode_curve(stream: &[u8], p: usize, kind: u8) -> Option<(CurveGeometry, usize)> {
192 for sh in SHIFTS {
193 let b = p + sh;
194 let geom = match kind {
195 0x1e => line(stream, b, DecodeContext::Scanner),
196 0x1f => circle(stream, b, DecodeContext::Scanner),
197 0x20 => ellipse(stream, b, DecodeContext::Scanner),
198 _ => None,
199 };
200 if let Some(geometry) = geom {
201 return Some((geometry, sh));
202 }
203 }
204 None
205}
206
207pub(crate) fn decode_surface_record(
209 record: &[u8],
210 kind: u8,
211 shift: usize,
212) -> Option<SurfaceGeometry> {
213 let b = shift;
214 match kind {
215 0x32 => plane(record, b, DecodeContext::Graph),
216 0x33 => cylinder(record, b, DecodeContext::Graph),
217 0x34 => cone(record, b, DecodeContext::Graph),
218 0x35 => sphere(record, b, DecodeContext::Graph),
219 0x36 => torus(record, b, DecodeContext::Graph),
220 _ => None,
221 }
222}
223
224pub(crate) fn decode_curve_record(record: &[u8], kind: u8, shift: usize) -> Option<CurveGeometry> {
226 let b = shift;
227 match kind {
228 0x1e => line(record, b, DecodeContext::Graph),
229 0x1f => circle(record, b, DecodeContext::Graph),
230 0x20 => ellipse(record, b, DecodeContext::Graph),
231 _ => None,
232 }
233}
234
235fn plane(s: &[u8], b: usize, context: DecodeContext) -> Option<SurfaceGeometry> {
238 let origin = read_vec3(s, b + 19)?;
239 let normal = read_vec3(s, b + 43)?;
240 let x_axis = read_vec3(s, b + 67)?;
241 if !is_orthonormal_frame(normal, x_axis) || !valid_position(origin, context) {
242 return None;
243 }
244 Some(SurfaceGeometry::Plane {
245 origin: mm_point(origin),
246 normal: vec3(normal),
247 u_axis: vec3(x_axis),
248 })
249}
250
251fn cylinder(s: &[u8], b: usize, context: DecodeContext) -> Option<SurfaceGeometry> {
252 let origin = read_vec3(s, b + 19)?;
253 let axis = read_vec3(s, b + 43)?;
254 let radius = read_f64(s, b + 67)?;
255 let x_axis = read_vec3(s, b + 75)?;
256 if !is_orthonormal_frame(axis, x_axis)
257 || !valid_position(origin, context)
258 || !valid_radius(radius, context)
259 {
260 return None;
261 }
262 Some(SurfaceGeometry::Cylinder {
263 origin: mm_point(origin),
264 axis: vec3(axis),
265 ref_direction: vec3(x_axis),
266 radius: radius * 1000.0,
267 })
268}
269
270fn cone(s: &[u8], b: usize, context: DecodeContext) -> Option<SurfaceGeometry> {
271 let origin = read_vec3(s, b + 19)?;
272 let axis = read_vec3(s, b + 43)?;
273 let radius = read_f64(s, b + 67)?;
274 let sin_half = read_f64(s, b + 75)?;
275 let cos_half = read_f64(s, b + 83)?;
276 let x_axis = read_vec3(s, b + 91)?;
277 if !is_orthonormal_frame(axis, x_axis)
278 || !valid_position(origin, context)
279 || !valid_cone_radius(radius, context)
280 {
281 return None;
282 }
283 if !sin_half.is_finite()
286 || !cos_half.is_finite()
287 || sin_half == 0.0
288 || cos_half == 0.0
289 || (sin_half * sin_half + cos_half * cos_half - 1.0).abs() > 1.0e-6
290 {
291 return None;
292 }
293 Some(SurfaceGeometry::Cone {
294 origin: mm_point(origin),
295 axis: vec3(axis),
296 ref_direction: vec3(x_axis),
297 radius: radius * 1000.0,
298 ratio: 1.0,
299 half_angle: sin_half.abs().atan2(cos_half.abs()),
300 })
301}
302
303fn sphere(s: &[u8], b: usize, context: DecodeContext) -> Option<SurfaceGeometry> {
304 let center = read_vec3(s, b + 19)?;
305 let radius = read_f64(s, b + 43)?;
306 let axis = read_vec3(s, b + 51)?;
307 let x_axis = read_vec3(s, b + 75)?;
308 if !is_orthonormal_frame(axis, x_axis)
309 || !valid_position(center, context)
310 || !valid_radius(radius, context)
311 {
312 return None;
313 }
314 Some(SurfaceGeometry::Sphere {
315 center: mm_point(center),
316 axis: vec3(axis),
317 ref_direction: vec3(x_axis),
318 radius: radius * 1000.0,
319 })
320}
321
322fn torus(s: &[u8], b: usize, context: DecodeContext) -> Option<SurfaceGeometry> {
323 let center = read_vec3(s, b + 19)?;
324 let axis = read_vec3(s, b + 43)?;
325 let major = read_f64(s, b + 67)?;
326 let minor = read_f64(s, b + 75)?;
327 let x_axis = read_vec3(s, b + 83)?;
328 if !is_orthonormal_frame(axis, x_axis)
331 || !valid_position(center, context)
332 || !valid_radius(major, context)
333 || !valid_radius(minor, context)
334 {
335 return None;
336 }
337 Some(SurfaceGeometry::Torus {
338 center: mm_point(center),
339 axis: vec3(axis),
340 ref_direction: vec3(x_axis),
341 major_radius: major * 1000.0,
342 minor_radius: minor * 1000.0,
343 })
344}
345
346fn line(s: &[u8], b: usize, context: DecodeContext) -> Option<CurveGeometry> {
349 let origin = read_vec3(s, b + 19)?;
350 let direction = read_vec3(s, b + 43)?;
351 if !is_unit(direction) || !valid_position(origin, context) {
352 return None;
353 }
354 Some(CurveGeometry::Line {
355 origin: mm_point(origin),
356 direction: vec3(direction),
357 })
358}
359
360fn circle(s: &[u8], b: usize, context: DecodeContext) -> Option<CurveGeometry> {
361 let center = read_vec3(s, b + 19)?;
362 let normal = read_vec3(s, b + 43)?;
363 let x_axis = read_vec3(s, b + 67)?;
364 let radius = read_f64(s, b + 91)?;
365 if !is_orthonormal_frame(normal, x_axis)
366 || !valid_position(center, context)
367 || !valid_radius(radius, context)
368 {
369 return None;
370 }
371 Some(CurveGeometry::Circle {
372 center: mm_point(center),
373 axis: vec3(normal),
374 ref_direction: vec3(x_axis),
375 radius: radius * 1000.0,
376 })
377}
378
379fn ellipse(s: &[u8], b: usize, context: DecodeContext) -> Option<CurveGeometry> {
380 let center = read_vec3(s, b + 19)?;
381 let normal = read_vec3(s, b + 43)?;
382 let x_axis = read_vec3(s, b + 67)?;
383 let major = read_f64(s, b + 91)?;
384 let minor = read_f64(s, b + 99)?;
385 if !is_orthonormal_frame(normal, x_axis) || !valid_position(center, context) {
386 return None;
387 }
388 if !valid_radius(major, context) || !valid_radius(minor, context) || minor > major {
389 return None;
390 }
391 Some(CurveGeometry::Ellipse {
392 center: mm_point(center),
393 axis: vec3(normal),
394 major_direction: vec3(x_axis),
395 major_radius: major * 1000.0,
396 minor_radius: minor * 1000.0,
397 })
398}
399
400fn is_unit(v: [f64; 3]) -> bool {
404 if !v.iter().all(|c| c.is_finite()) {
405 return false;
406 }
407 let n2 = v[0] * v[0] + v[1] * v[1] + v[2] * v[2];
408 (n2 - 1.0).abs() < 1.0e-6
409}
410
411fn is_orthonormal_frame(axis: [f64; 3], x_axis: [f64; 3]) -> bool {
413 is_unit(axis)
414 && is_unit(x_axis)
415 && (axis[0] * x_axis[0] + axis[1] * x_axis[1] + axis[2] * x_axis[2]).abs() < 1.0e-6
416}
417
418fn valid_position(v: [f64; 3], context: DecodeContext) -> bool {
419 v.iter().all(|coordinate| {
420 coordinate.is_finite()
421 && (*coordinate * 1000.0).is_finite()
422 && (matches!(context, DecodeContext::Graph) || coordinate.abs() < 1.0e3)
423 })
424}
425
426fn valid_radius(radius: f64, context: DecodeContext) -> bool {
427 radius.is_finite()
428 && (radius * 1000.0).is_finite()
429 && radius > 0.0
430 && (matches!(context, DecodeContext::Graph) || (radius > 1.0e-9 && radius < 1.0e3))
431}
432
433fn valid_cone_radius(radius: f64, context: DecodeContext) -> bool {
434 radius.is_finite()
435 && (radius * 1000.0).is_finite()
436 && radius >= 0.0
437 && (matches!(context, DecodeContext::Graph) || radius <= 1.0e3)
438}
439
440fn mm_point(v: [f64; 3]) -> Point3 {
441 Point3::new(v[0] * 1000.0, v[1] * 1000.0, v[2] * 1000.0)
442}
443
444fn vec3(v: [f64; 3]) -> Vector3 {
445 Vector3::new(v[0], v[1], v[2])
446}