use crate::bridge::preview_render::{PREVIEW_LIGHT_PITCH, PREVIEW_LIGHT_YAW};
use indicatrix::{
color::metrics::evaluate_gem_optical_metrics,
geometry::plane::GpuFacetPlane,
optics::{materials::GemMaterial, raytracer::LightingPreset},
};
use indicatrix_vault::{db::sqlite::Database, model::material_match::RiPresetCandidate};
const SCORE_PITCHES_DEG: [f32; 3] = [90.0, 60.0, 30.0];
const SCORE_PRESET: LightingPreset = LightingPreset::IsoHemisphere;
#[must_use]
pub fn balanced_loss(planes: &[GpuFacetPlane], material: &GemMaterial) -> f32 {
let environment = SCORE_PRESET.studio(1.0, PREVIEW_LIGHT_YAW, PREVIEW_LIGHT_PITCH);
let total: f32 = SCORE_PITCHES_DEG
.iter()
.map(|pitch| {
let m = evaluate_gem_optical_metrics(
planes,
material,
0.0,
pitch.to_radians(),
environment,
);
(m.windowing_pct + m.extinction_pct + (100.0 - m.brilliance_pct)) / 3.0
})
.sum();
total / SCORE_PITCHES_DEG.len() as f32
}
#[must_use]
pub fn best_balanced(planes: &[GpuFacetPlane], shortlist: &[&RiPresetCandidate]) -> usize {
let mut best = (0, f32::INFINITY);
for (index, candidate) in shortlist.iter().enumerate() {
let Some(material) = GemMaterial::by_name(&candidate.name) else {
continue;
};
let material = indicatrix::render_setup::material_for_stone(material, 0.0, planes);
let loss = balanced_loss(planes, &material);
if loss.is_finite() && loss < best.1 {
best = (index, loss);
}
}
best.0
}
pub fn ensure_balanced_material(
db: &Database,
entry_id: i64,
target_ri: f64,
candidates: &[RiPresetCandidate],
tolerance: f64,
planes: &[GpuFacetPlane],
) -> anyhow::Result<Option<String>> {
db.ensure_preview_material_by(
entry_id,
target_ri,
candidates,
tolerance,
&mut |shortlist| best_balanced(planes, shortlist),
)
}