use crate::numerical::scalar;
use crate::Vec3;
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
pub enum SurfaceType {
Plane,
Sphere,
Cylinder,
Cone,
Torus,
}
impl SurfaceType {
pub fn name(self) -> &'static str {
match self {
Self::Plane => "plane",
Self::Sphere => "sphere",
Self::Cylinder => "cylinder",
Self::Cone => "cone",
Self::Torus => "torus",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
pub struct PlaneSurface {
pub origin: Vec3,
pub normal: Vec3,
}
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
pub struct SphereSurface {
pub center: Vec3,
pub radius: f64,
}
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
pub struct CylinderSurface {
pub axis_origin: Vec3,
pub axis: Vec3,
pub radius: f64,
}
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
pub struct ConeSurface {
pub apex: Vec3,
pub axis: Vec3,
pub half_angle: f64,
}
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
pub struct TorusSurface {
pub center: Vec3,
pub axis: Vec3,
pub major_radius: f64,
pub minor_radius: f64,
}
#[derive(Clone, Copy, Debug, PartialEq, Deserialize, Serialize)]
#[serde(tag = "type", content = "parameters")]
pub enum AnalyticSurface {
Plane(PlaneSurface),
Sphere(SphereSurface),
Cylinder(CylinderSurface),
Cone(ConeSurface),
Torus(TorusSurface),
}
impl AnalyticSurface {
pub fn surface_type(self) -> SurfaceType {
match self {
Self::Plane(_) => SurfaceType::Plane,
Self::Sphere(_) => SurfaceType::Sphere,
Self::Cylinder(_) => SurfaceType::Cylinder,
Self::Cone(_) => SurfaceType::Cone,
Self::Torus(_) => SurfaceType::Torus,
}
}
pub fn signed_distance(self, point: Vec3) -> f64 {
match self {
Self::Plane(s) => (point - s.origin).dot(s.normal),
Self::Sphere(s) => (point - s.center).length() - s.radius,
Self::Cylinder(s) => {
let q = point - s.axis_origin;
(q - s.axis * q.dot(s.axis)).length() - s.radius
}
Self::Cone(s) => {
let q = point - s.apex;
let h = q.dot(s.axis);
let radial = (q - s.axis * h).length();
radial * s.half_angle.cos() - h * s.half_angle.sin()
}
Self::Torus(s) => {
let q = point - s.center;
let z = q.dot(s.axis);
let rho = (q - s.axis * z).length();
((rho - s.major_radius).powi(2) + z * z).sqrt() - s.minor_radius
}
}
}
pub fn normal_at(self, point: Vec3) -> Option<Vec3> {
match self {
Self::Plane(s) => Some(s.normal),
Self::Sphere(s) => (point - s.center).normalized(),
Self::Cylinder(s) => {
let q = point - s.axis_origin;
(q - s.axis * q.dot(s.axis)).normalized()
}
Self::Cone(s) => {
let q = point - s.apex;
let h = q.dot(s.axis);
let radial = (q - s.axis * h).normalized()?;
(radial * s.half_angle.cos() - s.axis * s.half_angle.sin()).normalized()
}
Self::Torus(s) => {
let q = point - s.center;
let z = q.dot(s.axis);
let radial = q - s.axis * z;
let rho = radial.length();
if rho <= scalar::MIN_NORMALIZABLE_NORM {
return None;
}
let tube = radial * (1.0 - s.major_radius / rho) + s.axis * z;
tube.normalized()
}
}
}
pub fn is_valid(self) -> bool {
match self {
Self::Plane(s) => s.origin.is_finite() && unit(s.normal),
Self::Sphere(s) => s.center.is_finite() && s.radius.is_finite() && s.radius > 0.0,
Self::Cylinder(s) => {
s.axis_origin.is_finite() && unit(s.axis) && s.radius.is_finite() && s.radius > 0.0
}
Self::Cone(s) => {
s.apex.is_finite()
&& unit(s.axis)
&& s.half_angle.is_finite()
&& s.half_angle > 0.0
&& s.half_angle < std::f64::consts::FRAC_PI_2
}
Self::Torus(s) => {
s.center.is_finite()
&& unit(s.axis)
&& s.major_radius.is_finite()
&& s.minor_radius.is_finite()
&& s.major_radius > 0.0
&& s.minor_radius > 0.0
}
}
}
pub fn canonicalized(self, centroid: Vec3) -> Self {
match self {
Self::Plane(mut s) => {
s.normal = s.normal.normalized().unwrap_or(s.normal).canonicalized();
s.origin = centroid + s.normal * (s.origin - centroid).dot(s.normal);
Self::Plane(s)
}
Self::Sphere(s) => Self::Sphere(s),
Self::Cylinder(mut s) => {
s.axis = s.axis.normalized().unwrap_or(s.axis).canonicalized();
s.axis_origin += s.axis * (centroid - s.axis_origin).dot(s.axis);
Self::Cylinder(s)
}
Self::Cone(mut s) => {
s.axis = s.axis.normalized().unwrap_or(s.axis);
Self::Cone(s)
}
Self::Torus(mut s) => {
s.axis = s.axis.normalized().unwrap_or(s.axis).canonicalized();
Self::Torus(s)
}
}
}
}
fn unit(v: Vec3) -> bool {
v.is_finite() && (v.length() - 1.0).abs() <= scalar::UNIT_LENGTH_TOLERANCE
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub enum MetadataTrust {
Exact,
StrongHint,
InitialGuess,
TypeOnly,
Unknown,
}
impl Default for MetadataTrust {
fn default() -> Self {
Self::Unknown
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Deserialize, Serialize)]
pub struct ConstraintMask {
pub origin_or_center: bool,
pub axis_or_normal: bool,
pub radius: bool,
pub major_radius: bool,
pub angle: bool,
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct SurfaceConstraints {
pub initial: Option<AnalyticSurface>,
pub fixed: ConstraintMask,
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub enum SurfaceHint {
Unknown,
KnownType {
surface_type: SurfaceType,
},
InitialGuess {
surface: AnalyticSurface,
trust: MetadataTrust,
},
Constrained {
surface_type: SurfaceType,
constraints: SurfaceConstraints,
trust: MetadataTrust,
},
ExactCandidate {
surface: AnalyticSurface,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
pub enum FitPath {
GenericRecognition,
KnownTypeFit,
HintReused,
ExactCandidateReused,
ConstrainedRefinement,
UnconstrainedRefinement,
HintRejectedFallback,
}
#[derive(Clone, Debug, Default, PartialEq, Deserialize, Serialize)]
pub struct FitMetrics {
pub rms_error: f64,
pub max_error: f64,
pub rms_normal_error: f64,
pub max_normal_error: f64,
pub support_triangles: usize,
pub supported_area: f64,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Deserialize, Serialize)]
pub struct GeometricError {
pub rms_position: f64,
pub max_position: f64,
pub rms_normal_radians: f64,
pub max_normal_radians: f64,
}
impl From<&FitMetrics> for GeometricError {
fn from(metrics: &FitMetrics) -> Self {
Self {
rms_position: metrics.rms_error,
max_position: metrics.max_error,
rms_normal_radians: metrics.rms_normal_error,
max_normal_radians: metrics.max_normal_error,
}
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Deserialize, Serialize)]
#[serde(default)]
pub struct SurfaceParameterDelta {
pub origin_or_center: Option<f64>,
pub axis_or_normal_radians: Option<f64>,
pub radius: Option<f64>,
pub major_radius: Option<f64>,
pub minor_radius: Option<f64>,
pub angle_radians: Option<f64>,
}
impl SurfaceParameterDelta {
pub fn between(initial: AnalyticSurface, refined: AnalyticSurface) -> Option<Self> {
fn angle(a: Vec3, b: Vec3, oriented: bool) -> f64 {
let dot = a.dot(b).clamp(-1.0, 1.0);
(if oriented { dot } else { dot.abs() }).acos()
}
fn line_distance(p: Vec3, a: Vec3, q: Vec3, b: Vec3) -> f64 {
let cross = a.cross(b);
let length = cross.length();
if length > scalar::PARALLEL_LINE_CROSS_NORM {
(q - p).dot(cross).abs() / length
} else {
let delta = q - p;
(delta - a * delta.dot(a)).length()
}
}
Some(match (initial, refined) {
(AnalyticSurface::Plane(a), AnalyticSurface::Plane(b)) => Self {
origin_or_center: Some((b.origin - a.origin).dot(a.normal).abs()),
axis_or_normal_radians: Some(angle(a.normal, b.normal, false)),
..Default::default()
},
(AnalyticSurface::Sphere(a), AnalyticSurface::Sphere(b)) => Self {
origin_or_center: Some(a.center.distance(b.center)),
radius: Some((a.radius - b.radius).abs()),
..Default::default()
},
(AnalyticSurface::Cylinder(a), AnalyticSurface::Cylinder(b)) => Self {
origin_or_center: Some(line_distance(a.axis_origin, a.axis, b.axis_origin, b.axis)),
axis_or_normal_radians: Some(angle(a.axis, b.axis, false)),
radius: Some((a.radius - b.radius).abs()),
..Default::default()
},
(AnalyticSurface::Cone(a), AnalyticSurface::Cone(b)) => Self {
origin_or_center: Some(a.apex.distance(b.apex)),
axis_or_normal_radians: Some(angle(a.axis, b.axis, true)),
angle_radians: Some((a.half_angle - b.half_angle).abs()),
..Default::default()
},
(AnalyticSurface::Torus(a), AnalyticSurface::Torus(b)) => Self {
origin_or_center: Some(a.center.distance(b.center)),
axis_or_normal_radians: Some(angle(a.axis, b.axis, false)),
radius: Some((a.minor_radius - b.minor_radius).abs()),
major_radius: Some((a.major_radius - b.major_radius).abs()),
minor_radius: Some((a.minor_radius - b.minor_radius).abs()),
..Default::default()
},
_ => return None,
})
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Deserialize, Serialize)]
#[serde(default)]
pub struct PhaseTimings {
pub metadata_inspection_seconds: Option<f64>,
pub candidate_generation_seconds: Option<f64>,
pub candidate_evaluation_seconds: Option<f64>,
pub region_growth_seconds: Option<f64>,
pub refinement_seconds: Option<f64>,
pub validation_seconds: Option<f64>,
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct FitDiagnostics {
pub path: FitPath,
pub supplied_surface: Option<AnalyticSurface>,
pub fixed_parameters: ConstraintMask,
pub parameters_refined: bool,
pub generic_classification_skipped: bool,
pub exact_parameters_reused: bool,
pub hypotheses_generated: usize,
pub candidates_evaluated: usize,
pub reason: String,
pub rejected_competitors: Vec<(SurfaceType, String)>,
#[serde(default)]
pub metadata_trust: MetadataTrust,
#[serde(default)]
pub initial_error: Option<GeometricError>,
#[serde(default)]
pub refined_error: Option<GeometricError>,
#[serde(default)]
pub parameter_delta: Option<SurfaceParameterDelta>,
#[serde(default)]
pub phase_timings: PhaseTimings,
}
impl Default for FitDiagnostics {
fn default() -> Self {
Self {
path: FitPath::GenericRecognition,
supplied_surface: None,
fixed_parameters: ConstraintMask::default(),
parameters_refined: false,
generic_classification_skipped: false,
exact_parameters_reused: false,
hypotheses_generated: 0,
candidates_evaluated: 0,
reason: String::new(),
rejected_competitors: Vec::new(),
metadata_trust: MetadataTrust::Unknown,
initial_error: None,
refined_error: None,
parameter_delta: None,
phase_timings: PhaseTimings::default(),
}
}
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct SurfaceFitResult {
pub surface: AnalyticSurface,
pub orientation: i8,
pub metrics: FitMetrics,
pub confidence: f64,
pub diagnostics: FitDiagnostics,
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct SurfaceRegion {
pub surface: AnalyticSurface,
pub orientation: i8,
pub triangle_indices: Vec<usize>,
pub metrics: FitMetrics,
pub confidence: f64,
pub diagnostics: FitDiagnostics,
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct UnresolvedRegionDiagnostic {
pub triangle_indices: Vec<usize>,
pub source_face_id: Option<u64>,
pub source_face_name: Option<String>,
pub source_surface_id: Option<String>,
pub reason: String,
}
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
pub struct RecognitionResult {
pub regions: Vec<SurfaceRegion>,
pub unresolved_triangles: Vec<usize>,
#[serde(default)]
pub unresolved_diagnostics: Vec<UnresolvedRegionDiagnostic>,
}