use crate::gui::library::detail::reconstruct_planes;
use glam::DVec3;
use indicatrix::geometry::{GpuFacetPlane, cuts::FacetSpec, girdle, stone_metrics};
use indicatrix_vault::{
db::sqlite::DEFAULT_SHAPES,
model::{detail::FacetingDiagramDetail, entry::FullDiagramRecord},
};
pub fn apply_measured_metadata(detail: &mut FacetingDiagramDetail) {
if detail.angle_settings_table.is_empty() {
return;
}
let facet_specs: Vec<FacetSpec> = detail
.angle_settings_table
.iter()
.map(|a| FacetSpec {
facet: a.facet.clone(),
angle: a.angle.clone(),
index: a.index.clone(),
notes: a.notes.clone(),
})
.collect();
let planes = reconstruct_planes(None, detail.index_gear.as_deref(), &facet_specs);
let dvec_planes: Vec<(DVec3, f64)> = planes
.iter()
.map(|p| {
(
DVec3::new(
f64::from(p.normal[0]),
f64::from(p.normal[1]),
f64::from(p.normal[2]),
),
-f64::from(p.d),
)
})
.collect();
let Some(m) = stone_metrics::measure_solid(&dvec_planes) else {
return;
};
let w = m.width_axis;
if w < 1e-9 {
return;
}
detail.lw_ratio = Some((m.length_axis / w).to_string());
detail.hw_ratio = Some((m.total_height / w).to_string());
detail.cw_ratio = m.crown_height.map(|c| (c / w).to_string());
detail.pw_ratio = m.pavilion_depth.map(|p| (p / w).to_string());
detail.volume = Some((m.volume / (w * w * w)).to_string());
detail.shape = detail
.shape
.take()
.or_else(|| classify_shape(&planes, m.length_axis / w));
}
pub fn merge_reimport_metadata(fresh: &mut FacetingDiagramDetail, existing: &FullDiagramRecord) {
if fresh.page_url.is_empty() {
fresh.page_url.clone_from(&existing.page_url);
}
fresh.diagram_image_name = fresh
.diagram_image_name
.take()
.or_else(|| existing.diagram_image_name.clone());
fresh.diagram_image_data = fresh
.diagram_image_data
.take()
.or_else(|| existing.diagram_image_data.clone());
fresh.competition_diagram = fresh
.competition_diagram
.take()
.or_else(|| existing.competition_diagram.clone());
fresh.lw_ratio = fresh.lw_ratio.take().or_else(|| existing.lw_ratio.clone());
fresh.refractive_index = fresh
.refractive_index
.take()
.or_else(|| existing.refractive_index.clone());
fresh.index_gear = fresh
.index_gear
.take()
.or_else(|| existing.index_gear.clone());
fresh.volume = fresh.volume.take().or_else(|| existing.volume.clone());
fresh.facets_count = fresh
.facets_count
.take()
.or_else(|| existing.facets_count.clone());
fresh.shape = fresh.shape.take().or_else(|| existing.shape.clone());
fresh.designer_info = fresh
.designer_info
.take()
.or_else(|| existing.designer_info.clone());
fresh.hw_ratio = fresh.hw_ratio.take().or_else(|| existing.hw_ratio.clone());
fresh.tw_ratio = fresh.tw_ratio.take().or_else(|| existing.tw_ratio.clone());
fresh.uw_ratio = fresh.uw_ratio.take().or_else(|| existing.uw_ratio.clone());
fresh.pw_ratio = fresh.pw_ratio.take().or_else(|| existing.pw_ratio.clone());
fresh.cw_ratio = fresh.cw_ratio.take().or_else(|| existing.cw_ratio.clone());
fresh.symmetry_order = fresh
.symmetry_order
.take()
.or_else(|| existing.symmetry_order.clone());
fresh.mirror_symmetry = fresh.mirror_symmetry.or(existing.mirror_symmetry);
fresh.designer = fresh.designer.take().or_else(|| existing.designer.clone());
fresh.source_citation = fresh
.source_citation
.take()
.or_else(|| existing.source_citation.clone());
fresh.pdf_file = fresh.pdf_file.take().or_else(|| existing.pdf_file.clone());
fresh.gem_file = fresh.gem_file.take().or_else(|| existing.gem_file.clone());
fresh.shape_category = fresh
.shape_category
.take()
.or_else(|| existing.shape_category.clone());
}
pub(super) fn classify_shape(planes: &[GpuFacetPlane], lw: f64) -> Option<String> {
const ROUND_LW_MAX: f64 = 1.03;
const POLYGON_LW_MAX: f64 = 1.20;
const ROUND_MIN_SIDES: usize = 12;
let sides = girdle::classify_girdle_plane_indices(planes).len();
let name = match sides {
3 if lw <= POLYGON_LW_MAX => "Triangle",
4 if lw <= ROUND_LW_MAX => "Square",
6 if lw <= POLYGON_LW_MAX => "Hexagon",
8 if lw <= ROUND_LW_MAX => "Octagon",
n if n >= ROUND_MIN_SIDES && lw <= ROUND_LW_MAX => "Round",
_ => return None,
};
DEFAULT_SHAPES
.iter()
.find(|s| **s == name)
.map(|s| (*s).to_string())
}