use crate::{
AngleItem, MainWindow, TiltModel, ViewportModel,
bridge::render_thread::{PlanesOwner, RenderContext},
gui::editor::material_lookup::{MATERIAL_MATCH_TOLERANCE, material_for_refractive_index},
};
use indicatrix::{
geometry::{
cuts::{FacetSpec, StandardGemCuts},
plane::GpuFacetPlane,
},
optics::materials::GemMaterial,
};
use indicatrix_cut_core::material::{BuiltinMaterials, MaterialLookup};
use slint::ComponentHandle;
use std::sync::{Arc, Mutex};
use tracing::info;
pub(super) struct ReconstructedPlanesInput<'a> {
pub(super) shape: Option<&'a str>,
pub(super) index_gear: Option<&'a str>,
pub(super) angle_items: &'a [AngleItem],
pub(super) refractive_index: Option<&'a str>,
pub(super) preview_material: Option<&'a str>,
pub(super) real_design: Option<(Vec<GpuFacetPlane>, u32, f32)>,
}
pub(super) fn planes_gear_and_reference_angle(
real_design: Option<(Vec<GpuFacetPlane>, u32, f32)>,
shape: Option<&str>,
index_gear: Option<&str>,
angle_items: &[AngleItem],
) -> (Vec<GpuFacetPlane>, u32, f32) {
if let Some(resolved) = real_design {
return resolved;
}
let facet_specs: Vec<FacetSpec> = angle_items
.iter()
.filter(|a| a.second_line.is_empty())
.map(|a| FacetSpec {
facet: a.facet.to_string(),
angle: a.angle.to_string(),
index: a.index_val.to_string(),
notes: a.notes.to_string(),
})
.collect();
let planes = reconstruct_planes(shape, index_gear, &facet_specs);
let gear_teeth: u32 = index_gear
.and_then(|g| g.trim().parse().ok())
.filter(|&g| g > 0)
.unwrap_or(96);
(planes, gear_teeth, 0.0)
}
pub(super) fn apply_reconstructed_planes(
ui: &MainWindow,
render_ctx: &Arc<Mutex<RenderContext>>,
entry_id: i64,
input: ReconstructedPlanesInput<'_>,
) {
let ReconstructedPlanesInput {
shape,
index_gear,
angle_items,
refractive_index,
preview_material,
real_design,
} = input;
let (planes, gear_teeth, gear_reference_angle) =
planes_gear_and_reference_angle(real_design, shape, index_gear, angle_items);
info!(
"Reconstructed {} 3D facet planes for diagram #{}",
planes.len(),
entry_id
);
let mut ctx = RenderContext::lock(render_ctx);
let claimed = ctx.claim_active_planes(
std::sync::Arc::new(planes),
Some((gear_teeth, gear_reference_angle)),
PlanesOwner::Catalogue { entry_id },
);
let resolved_material = if claimed {
let resolved = apply_catalogue_material(&mut ctx, refractive_index, preview_material);
ctx.dirty = true;
crate::gui::editor::push_viewport_owner(ui, ctx.planes_owner);
resolved
} else {
None
};
drop(ctx);
if let Some(name) = resolved_material {
let options = ui.global::<ViewportModel>().get_material_options();
if let Some(index) = crate::gui::startup_settings::find_option_index(&options, &name) {
ui.global::<ViewportModel>()
.set_selected_material_index(index);
}
}
if !claimed {
return;
}
if ui.global::<TiltModel>().get_dialog_open() {
ui.global::<TiltModel>().invoke_request_tilt_profile_axes();
}
}
fn lookup_named_material(ctx: &RenderContext, name: &str) -> Option<GemMaterial> {
ctx.custom_materials
.iter()
.find(|m| m.name.eq_ignore_ascii_case(name))
.cloned()
.or_else(|| BuiltinMaterials.lookup(name))
}
fn apply_catalogue_material(
ctx: &mut RenderContext,
refractive_index: Option<&str>,
preview_material: Option<&str>,
) -> Option<String> {
if let Some(name) = preview_material.map(str::trim).filter(|s| !s.is_empty()) {
return if let Some(gem) = lookup_named_material(ctx, name) {
ctx.material_unresolved = None;
ctx.material_name = name.to_string();
ctx.material_override = Some(gem);
Some(name.to_string())
} else {
ctx.material_name.clear();
ctx.material_override = None;
ctx.material_unresolved = Some(format!(
"This design's material '{name}' is not a built-in preset or a saved \
custom material, so there is nothing to trace -- pick a material in \
the Render Material dropdown above, or re-save the missing custom \
material."
));
None
};
}
let n_d = refractive_index.and_then(|text| text.trim().parse::<f64>().ok());
let Some(n_d) = n_d.filter(|v| v.is_finite() && *v > 1.0) else {
ctx.material_name.clear();
ctx.material_override = None;
ctx.material_unresolved = Some(
"This design records no usable refractive index, so there is no honest \
material to render it in. Pick one in the Render Material dropdown above."
.to_string(),
);
return None;
};
let Some((name, gem)) = material_for_refractive_index(n_d) else {
ctx.material_name.clear();
ctx.material_override = None;
ctx.material_unresolved = Some(format!(
"This design's refractive index ({n_d:.4}) matches no built-in preset within \
{MATERIAL_MATCH_TOLERANCE:.2}. Pick a material in the Render Material dropdown \
above -- rendering it as something else would give you the optics of a \
different stone."
));
return None;
};
ctx.material_unresolved = None;
ctx.material_name.clone_from(&name);
ctx.material_override = Some(gem);
Some(name)
}
pub fn reconstruct_planes(
shape: Option<&str>,
index_gear: Option<&str>,
facet_specs: &[FacetSpec],
) -> Vec<GpuFacetPlane> {
let shape_str = shape.unwrap_or_default().to_lowercase();
let gear_num: u32 = index_gear.unwrap_or_default().parse().unwrap_or(96);
if shape_str.contains("emerald") || shape_str.contains("baguette") || shape_str.contains("rect")
{
StandardGemCuts::emerald_cut()
} else if !facet_specs.is_empty() {
StandardGemCuts::from_database_angles(facet_specs, gear_num)
} else {
StandardGemCuts::standard_round_brilliant()
}
}