1use crate::numerical::scalar;
2use crate::Vec3;
3use serde::{Deserialize, Serialize};
4
5#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
6pub enum SurfaceType {
8 Plane,
10 Sphere,
12 Cylinder,
14 Cone,
16 Torus,
18}
19
20impl SurfaceType {
21 pub fn name(self) -> &'static str {
23 match self {
24 Self::Plane => "plane",
25 Self::Sphere => "sphere",
26 Self::Cylinder => "cylinder",
27 Self::Cone => "cone",
28 Self::Torus => "torus",
29 }
30 }
31}
32
33#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
34pub struct PlaneSurface {
36 pub origin: Vec3,
38 pub normal: Vec3,
40}
41#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
42pub struct SphereSurface {
44 pub center: Vec3,
46 pub radius: f64,
48}
49#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
50pub struct CylinderSurface {
52 pub axis_origin: Vec3,
54 pub axis: Vec3,
56 pub radius: f64,
58}
59#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
60pub struct ConeSurface {
62 pub apex: Vec3,
64 pub axis: Vec3,
66 pub half_angle: f64,
68}
69#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
70pub struct TorusSurface {
72 pub center: Vec3,
74 pub axis: Vec3,
76 pub major_radius: f64,
78 pub minor_radius: f64,
80}
81
82#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
83#[serde(tag = "type", content = "parameters")]
84pub enum AnalyticSurface {
86 Plane(PlaneSurface),
88 Sphere(SphereSurface),
90 Cylinder(CylinderSurface),
92 Cone(ConeSurface),
94 Torus(TorusSurface),
96}
97
98impl AnalyticSurface {
99 pub fn surface_type(self) -> SurfaceType {
101 match self {
102 Self::Plane(_) => SurfaceType::Plane,
103 Self::Sphere(_) => SurfaceType::Sphere,
104 Self::Cylinder(_) => SurfaceType::Cylinder,
105 Self::Cone(_) => SurfaceType::Cone,
106 Self::Torus(_) => SurfaceType::Torus,
107 }
108 }
109 pub fn signed_distance(self, point: Vec3) -> f64 {
115 match self {
116 Self::Plane(s) => (point - s.origin).dot(s.normal),
117 Self::Sphere(s) => (point - s.center).length() - s.radius,
118 Self::Cylinder(s) => {
119 let q = point - s.axis_origin;
120 (q - s.axis * q.dot(s.axis)).length() - s.radius
121 }
122 Self::Cone(s) => {
123 let q = point - s.apex;
124 let h = q.dot(s.axis);
125 let radial = (q - s.axis * h).length();
126 radial * s.half_angle.cos() - h * s.half_angle.sin()
127 }
128 Self::Torus(s) => {
129 let q = point - s.center;
130 let z = q.dot(s.axis);
131 let rho = (q - s.axis * z).length();
132 ((rho - s.major_radius).powi(2) + z * z).sqrt() - s.minor_radius
133 }
134 }
135 }
136 pub fn normal_at(self, point: Vec3) -> Option<Vec3> {
138 match self {
139 Self::Plane(s) => Some(s.normal),
140 Self::Sphere(s) => (point - s.center).normalized(),
141 Self::Cylinder(s) => {
142 let q = point - s.axis_origin;
143 (q - s.axis * q.dot(s.axis)).normalized()
144 }
145 Self::Cone(s) => {
146 let q = point - s.apex;
147 let h = q.dot(s.axis);
148 let radial = (q - s.axis * h).normalized()?;
149 (radial * s.half_angle.cos() - s.axis * s.half_angle.sin()).normalized()
150 }
151 Self::Torus(s) => {
152 let q = point - s.center;
153 let z = q.dot(s.axis);
154 let radial = q - s.axis * z;
155 let rho = radial.length();
156 if rho <= scalar::MIN_NORMALIZABLE_NORM {
157 return None;
158 }
159 let tube = radial * (1.0 - s.major_radius / rho) + s.axis * z;
160 tube.normalized()
161 }
162 }
163 }
164 pub fn is_valid(self) -> bool {
166 match self {
167 Self::Plane(s) => s.origin.is_finite() && unit(s.normal),
168 Self::Sphere(s) => s.center.is_finite() && s.radius.is_finite() && s.radius > 0.0,
169 Self::Cylinder(s) => {
170 s.axis_origin.is_finite() && unit(s.axis) && s.radius.is_finite() && s.radius > 0.0
171 }
172 Self::Cone(s) => {
173 s.apex.is_finite()
174 && unit(s.axis)
175 && s.half_angle.is_finite()
176 && s.half_angle > 0.0
177 && s.half_angle < std::f64::consts::FRAC_PI_2
178 }
179 Self::Torus(s) => {
180 s.center.is_finite()
181 && unit(s.axis)
182 && s.major_radius.is_finite()
183 && s.minor_radius.is_finite()
184 && s.major_radius > 0.0
185 && s.minor_radius > 0.0
186 }
187 }
188 }
189 pub fn canonicalized(self, centroid: Vec3) -> Self {
192 match self {
193 Self::Plane(mut s) => {
194 s.normal = s.normal.normalized().unwrap_or(s.normal).canonicalized();
195 s.origin = centroid + s.normal * (s.origin - centroid).dot(s.normal);
196 Self::Plane(s)
197 }
198 Self::Sphere(s) => Self::Sphere(s),
199 Self::Cylinder(mut s) => {
200 s.axis = s.axis.normalized().unwrap_or(s.axis).canonicalized();
201 s.axis_origin += s.axis * (centroid - s.axis_origin).dot(s.axis);
202 Self::Cylinder(s)
203 }
204 Self::Cone(mut s) => {
205 s.axis = s.axis.normalized().unwrap_or(s.axis);
206 Self::Cone(s)
207 }
208 Self::Torus(mut s) => {
209 s.axis = s.axis.normalized().unwrap_or(s.axis).canonicalized();
210 Self::Torus(s)
211 }
212 }
213 }
214}
215fn unit(v: Vec3) -> bool {
216 v.is_finite() && (v.length() - 1.0).abs() <= scalar::UNIT_LENGTH_TOLERANCE
217}
218
219#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
220pub enum MetadataTrust {
222 Exact,
224 StrongHint,
226 InitialGuess,
228 TypeOnly,
230 Unknown,
232}
233
234impl Default for MetadataTrust {
235 fn default() -> Self {
236 Self::Unknown
237 }
238}
239
240#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
243pub struct ConstraintMask {
244 pub origin_or_center: bool,
246 pub axis_or_normal: bool,
248 pub radius: bool,
250 pub major_radius: bool,
252 pub angle: bool,
254}
255
256#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
257pub struct SurfaceConstraints {
259 pub initial: Option<AnalyticSurface>,
261 pub fixed: ConstraintMask,
263}
264
265#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
266pub enum SurfaceHint {
268 Unknown,
270 KnownType {
272 surface_type: SurfaceType,
274 },
275 InitialGuess {
277 surface: AnalyticSurface,
279 trust: MetadataTrust,
281 },
282 Constrained {
284 surface_type: SurfaceType,
286 constraints: SurfaceConstraints,
288 trust: MetadataTrust,
290 },
291 ExactCandidate {
293 surface: AnalyticSurface,
295 },
296}
297
298#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
299pub enum FitPath {
301 GenericRecognition,
303 KnownTypeFit,
305 HintReused,
307 ExactCandidateReused,
309 ConstrainedRefinement,
311 UnconstrainedRefinement,
313 HintRejectedFallback,
315}
316
317#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
318pub struct FitMetrics {
320 pub rms_error: f64,
322 pub max_error: f64,
324 pub rms_normal_error: f64,
326 pub max_normal_error: f64,
328 pub support_triangles: usize,
330 pub supported_area: f64,
332}
333
334#[derive(Clone, Copy, Debug, Default, PartialEq, Deserialize, Serialize)]
337pub struct GeometricError {
338 pub rms_position: f64,
340 pub max_position: f64,
342 pub rms_normal_radians: f64,
344 pub max_normal_radians: f64,
346}
347
348impl From<&FitMetrics> for GeometricError {
349 fn from(metrics: &FitMetrics) -> Self {
350 Self {
351 rms_position: metrics.rms_error,
352 max_position: metrics.max_error,
353 rms_normal_radians: metrics.rms_normal_error,
354 max_normal_radians: metrics.max_normal_error,
355 }
356 }
357}
358
359#[derive(Clone, Copy, Debug, Default, PartialEq, Deserialize, Serialize)]
362#[serde(default)]
363pub struct SurfaceParameterDelta {
364 pub origin_or_center: Option<f64>,
366 pub axis_or_normal_radians: Option<f64>,
368 pub radius: Option<f64>,
370 pub major_radius: Option<f64>,
372 pub minor_radius: Option<f64>,
374 pub angle_radians: Option<f64>,
376}
377
378impl SurfaceParameterDelta {
379 pub fn between(initial: AnalyticSurface, refined: AnalyticSurface) -> Option<Self> {
384 fn angle(a: Vec3, b: Vec3, oriented: bool) -> f64 {
385 let dot = a.dot(b).clamp(-1.0, 1.0);
386 (if oriented { dot } else { dot.abs() }).acos()
387 }
388 fn line_distance(p: Vec3, a: Vec3, q: Vec3, b: Vec3) -> f64 {
389 let cross = a.cross(b);
390 let length = cross.length();
391 if length > scalar::PARALLEL_LINE_CROSS_NORM {
392 (q - p).dot(cross).abs() / length
393 } else {
394 let delta = q - p;
395 (delta - a * delta.dot(a)).length()
396 }
397 }
398 Some(match (initial, refined) {
399 (AnalyticSurface::Plane(a), AnalyticSurface::Plane(b)) => Self {
400 origin_or_center: Some((b.origin - a.origin).dot(a.normal).abs()),
401 axis_or_normal_radians: Some(angle(a.normal, b.normal, false)),
402 ..Default::default()
403 },
404 (AnalyticSurface::Sphere(a), AnalyticSurface::Sphere(b)) => Self {
405 origin_or_center: Some(a.center.distance(b.center)),
406 radius: Some((a.radius - b.radius).abs()),
407 ..Default::default()
408 },
409 (AnalyticSurface::Cylinder(a), AnalyticSurface::Cylinder(b)) => Self {
410 origin_or_center: Some(line_distance(a.axis_origin, a.axis, b.axis_origin, b.axis)),
411 axis_or_normal_radians: Some(angle(a.axis, b.axis, false)),
412 radius: Some((a.radius - b.radius).abs()),
413 ..Default::default()
414 },
415 (AnalyticSurface::Cone(a), AnalyticSurface::Cone(b)) => Self {
416 origin_or_center: Some(a.apex.distance(b.apex)),
417 axis_or_normal_radians: Some(angle(a.axis, b.axis, true)),
418 angle_radians: Some((a.half_angle - b.half_angle).abs()),
419 ..Default::default()
420 },
421 (AnalyticSurface::Torus(a), AnalyticSurface::Torus(b)) => Self {
422 origin_or_center: Some(a.center.distance(b.center)),
423 axis_or_normal_radians: Some(angle(a.axis, b.axis, false)),
424 radius: Some((a.minor_radius - b.minor_radius).abs()),
425 major_radius: Some((a.major_radius - b.major_radius).abs()),
426 minor_radius: Some((a.minor_radius - b.minor_radius).abs()),
427 ..Default::default()
428 },
429 _ => return None,
430 })
431 }
432}
433
434#[derive(Clone, Copy, Debug, Default, PartialEq, Deserialize, Serialize)]
437#[serde(default)]
438pub struct PhaseTimings {
439 pub metadata_inspection_seconds: Option<f64>,
441 pub candidate_generation_seconds: Option<f64>,
443 pub candidate_evaluation_seconds: Option<f64>,
445 pub region_growth_seconds: Option<f64>,
447 pub refinement_seconds: Option<f64>,
449 pub validation_seconds: Option<f64>,
451}
452
453#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
454pub struct FitDiagnostics {
456 pub path: FitPath,
458 pub supplied_surface: Option<AnalyticSurface>,
460 pub fixed_parameters: ConstraintMask,
462 pub parameters_refined: bool,
464 pub generic_classification_skipped: bool,
466 pub exact_parameters_reused: bool,
468 pub hypotheses_generated: usize,
470 pub candidates_evaluated: usize,
472 pub reason: String,
474 pub rejected_competitors: Vec<(SurfaceType, String)>,
476 #[serde(default)]
479 pub metadata_trust: MetadataTrust,
480 #[serde(default)]
482 pub initial_error: Option<GeometricError>,
483 #[serde(default)]
485 pub refined_error: Option<GeometricError>,
486 #[serde(default)]
488 pub parameter_delta: Option<SurfaceParameterDelta>,
489 #[serde(default)]
490 pub phase_timings: PhaseTimings,
492}
493
494impl Default for FitDiagnostics {
495 fn default() -> Self {
496 Self {
497 path: FitPath::GenericRecognition,
498 supplied_surface: None,
499 fixed_parameters: ConstraintMask::default(),
500 parameters_refined: false,
501 generic_classification_skipped: false,
502 exact_parameters_reused: false,
503 hypotheses_generated: 0,
504 candidates_evaluated: 0,
505 reason: String::new(),
506 rejected_competitors: Vec::new(),
507 metadata_trust: MetadataTrust::Unknown,
508 initial_error: None,
509 refined_error: None,
510 parameter_delta: None,
511 phase_timings: PhaseTimings::default(),
512 }
513 }
514}
515
516#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
517pub struct SurfaceFitResult {
519 pub surface: AnalyticSurface,
521 pub orientation: i8,
523 pub metrics: FitMetrics,
525 pub confidence: f64,
527 pub diagnostics: FitDiagnostics,
529}
530
531#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
532pub struct SurfaceRegion {
534 pub surface: AnalyticSurface,
536 pub orientation: i8,
538 pub triangle_indices: Vec<usize>,
540 pub metrics: FitMetrics,
542 pub confidence: f64,
544 pub diagnostics: FitDiagnostics,
546}
547
548#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
549pub struct UnresolvedRegionDiagnostic {
552 pub triangle_indices: Vec<usize>,
554 pub source_face_id: Option<u64>,
556 pub source_face_name: Option<String>,
558 pub source_surface_id: Option<String>,
560 pub reason: String,
563}
564
565#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
566pub struct RecognitionResult {
568 pub regions: Vec<SurfaceRegion>,
570 pub unresolved_triangles: Vec<usize>,
572 #[serde(default)]
574 pub unresolved_diagnostics: Vec<UnresolvedRegionDiagnostic>,
575}