use super::types::MunsellSpec;
use super::polyhedron::ConvexPolyhedron;
#[derive(Debug, Clone)]
pub struct SemanticOverlay {
pub name: &'static str,
pub polyhedron: ConvexPolyhedron,
pub centroid: MunsellSpec,
pub sample_count: u32,
}
impl SemanticOverlay {
pub fn new(
name: &'static str,
vertices: &[(f64, f64, f64)],
faces: &[(usize, usize, usize)],
centroid: MunsellSpec,
sample_count: u32,
) -> Self {
Self {
name,
polyhedron: ConvexPolyhedron::from_arrays(vertices, faces),
centroid,
sample_count,
}
}
pub fn contains(&self, color: &MunsellSpec) -> bool {
let point = color.to_cartesian();
self.polyhedron.contains_point(&point)
}
pub fn contains_with_tolerance(&self, color: &MunsellSpec, tolerance: f64) -> bool {
let point = color.to_cartesian();
self.polyhedron.contains_point_with_tolerance(&point, tolerance)
}
pub fn distance_to_centroid(&self, color: &MunsellSpec) -> f64 {
color.distance_from(&self.centroid)
}
pub fn centroid_notation(&self) -> String {
self.centroid.to_notation()
}
}
#[derive(Debug, Clone)]
pub struct SemanticOverlayRegistry {
overlays: Vec<SemanticOverlay>,
}
impl SemanticOverlayRegistry {
pub fn new(overlays: Vec<SemanticOverlay>) -> Self {
Self { overlays }
}
pub fn all(&self) -> &[SemanticOverlay] {
&self.overlays
}
pub fn get(&self, name: &str) -> Option<&SemanticOverlay> {
let name_lower = name.to_lowercase();
self.overlays.iter().find(|o| o.name.to_lowercase() == name_lower)
}
pub fn matches(&self, color: &MunsellSpec, overlay_name: &str) -> bool {
self.get(overlay_name)
.map(|o| o.contains(color))
.unwrap_or(false)
}
pub fn matching_overlays(&self, color: &MunsellSpec) -> Vec<&SemanticOverlay> {
self.overlays
.iter()
.filter(|o| o.contains(color))
.collect()
}
pub fn best_match(&self, color: &MunsellSpec) -> Option<&SemanticOverlay> {
let matches = self.matching_overlays(color);
if matches.is_empty() {
return None;
}
matches
.into_iter()
.min_by(|a, b| {
let dist_a = a.distance_to_centroid(color);
let dist_b = b.distance_to_centroid(color);
dist_a.partial_cmp(&dist_b).unwrap_or(std::cmp::Ordering::Equal)
})
}
pub fn matching_overlays_ranked(&self, color: &MunsellSpec) -> Vec<(&SemanticOverlay, f64)> {
let mut matches: Vec<(&SemanticOverlay, f64)> = self
.overlays
.iter()
.filter(|o| o.contains(color))
.map(|o| (o, o.distance_to_centroid(color)))
.collect();
matches.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
matches
}
pub fn closest_overlay(&self, color: &MunsellSpec) -> Option<(&SemanticOverlay, f64)> {
self.overlays
.iter()
.map(|o| (o, o.distance_to_centroid(color)))
.min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal))
}
pub fn names(&self) -> Vec<&'static str> {
self.overlays.iter().map(|o| o.name).collect()
}
pub fn len(&self) -> usize {
self.overlays.len()
}
pub fn is_empty(&self) -> bool {
self.overlays.is_empty()
}
}