use crate::{
bridge::frame_cache::stone_width::StoneWidthCache,
settings::model::{LiveComputeTarget, LocalComputeTarget, LocalPreviewScale},
};
use glam::Vec3;
use indicatrix::{
geometry::{cuts::StandardGemCuts, plane::GpuFacetPlane},
optics::{
materials::{GemMaterial, OpticalCharacter},
raytracer::LightingPreset,
},
renderer::env_map::{EnvMapError, EnvironmentMap},
};
use indicatrix_net::client::Accumulator;
use std::sync::{Arc, Mutex};
pub struct RenderContext {
pub width: u32,
pub height: u32,
pub yaw: f32,
pub pitch: f32,
pub distance: f32,
pub light_yaw: f32,
pub light_pitch: f32,
pub material_name: String,
pub lighting_preset: LightingPreset,
pub target_samples: u32,
pub max_bounces: u32,
pub exposure: f32,
pub inclusion_sigma_s: f32,
pub c_axis_override: Option<Vec3>,
pub girdle_frosted: bool,
pub edge_rounding_radius: f32,
pub stone_width_mm: f32,
pub active_planes: Arc<Vec<GpuFacetPlane>>,
pub design_gear: Option<(u32, f32)>,
pub custom_materials: Arc<Vec<GemMaterial>>,
pub running: bool,
pub dirty: bool,
pub paused: bool,
pub tab_visible: bool,
pub denoise_enabled: bool,
pub remote_active: bool,
pub remote_accumulator: Option<Arc<Mutex<Accumulator>>>,
pub remote_reserved_samples: u32,
pub export_active: bool,
pub remote_render_samples: u32,
pub live_compute_target: LiveComputeTarget,
pub local_compute_target: LocalComputeTarget,
pub local_preview_scale: LocalPreviewScale,
pub camera_moving: bool,
pub env_map: Option<Arc<EnvironmentMap>>,
}
impl Default for RenderContext {
fn default() -> Self {
Self {
width: 800,
height: 600,
yaw: 0.60, pitch: 0.45, distance: 2.4,
light_yaw: 0.85, light_pitch: 0.95, material_name: "Diamond".to_string(),
lighting_preset: LightingPreset::RingLights,
target_samples: 256,
max_bounces: 12,
exposure: 1.0,
inclusion_sigma_s: 0.0,
c_axis_override: None,
girdle_frosted: false,
edge_rounding_radius: 0.0,
stone_width_mm: 0.0,
active_planes: Arc::new(StandardGemCuts::standard_round_brilliant()),
design_gear: None,
custom_materials: Arc::new(Vec::new()),
running: true,
dirty: true,
paused: false,
tab_visible: true,
denoise_enabled: true,
remote_active: false,
remote_accumulator: None,
remote_reserved_samples: 0,
export_active: false,
remote_render_samples: 512,
live_compute_target: LiveComputeTarget::Both,
local_compute_target: LocalComputeTarget::CpuGpu,
local_preview_scale: LocalPreviewScale::Off,
camera_moving: false,
env_map: None,
}
}
}
pub fn load_env_map(path: &str) -> Result<Arc<EnvironmentMap>, String> {
EnvironmentMap::from_hdr_file(path)
.map(Arc::new)
.map_err(|e: EnvMapError| e.to_string())
}
pub(super) struct FrameInputs {
pub(super) width: u32,
pub(super) height: u32,
pub(super) yaw: f32,
pub(super) pitch: f32,
pub(super) distance: f32,
pub(super) light_yaw: f32,
pub(super) light_pitch: f32,
pub(super) material_name: String,
pub(super) lighting_preset: LightingPreset,
pub(super) target_samples: u32,
pub(super) max_bounces: u32,
pub(super) exposure: f32,
pub(super) inclusion_sigma_s: f32,
pub(super) c_axis_override: Option<Vec3>,
pub(super) girdle_frosted: bool,
pub(super) edge_rounding_radius: f32,
pub(super) stone_width_mm: f32,
pub(super) active_planes: Arc<Vec<GpuFacetPlane>>,
pub(super) custom_materials: Arc<Vec<GemMaterial>>,
pub(super) running: bool,
pub(super) dirty: bool,
pub(super) paused: bool,
pub(super) tab_visible: bool,
pub(super) denoise_enabled: bool,
pub(super) remote_active: bool,
pub(super) remote_accumulator: Option<Arc<Mutex<Accumulator>>>,
pub(super) remote_reserved_samples: u32,
pub(super) export_active: bool,
pub(super) live_compute_target: LiveComputeTarget,
pub(super) local_compute_target: LocalComputeTarget,
pub(super) local_preview_scale: LocalPreviewScale,
pub(super) camera_moving: bool,
pub(super) env_map: Option<Arc<EnvironmentMap>>,
}
pub(super) fn snapshot_frame_inputs(ctx: &Arc<Mutex<RenderContext>>) -> FrameInputs {
let mut ctx = ctx
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
let dirty = ctx.dirty;
ctx.dirty = false;
FrameInputs {
width: ctx.width,
height: ctx.height,
yaw: ctx.yaw,
pitch: ctx.pitch,
distance: ctx.distance,
light_yaw: ctx.light_yaw,
light_pitch: ctx.light_pitch,
material_name: ctx.material_name.clone(),
lighting_preset: ctx.lighting_preset,
target_samples: ctx.target_samples,
max_bounces: ctx.max_bounces,
exposure: ctx.exposure,
inclusion_sigma_s: ctx.inclusion_sigma_s,
c_axis_override: ctx.c_axis_override,
girdle_frosted: ctx.girdle_frosted,
edge_rounding_radius: ctx.edge_rounding_radius,
stone_width_mm: ctx.stone_width_mm,
active_planes: Arc::clone(&ctx.active_planes),
custom_materials: Arc::clone(&ctx.custom_materials),
running: ctx.running,
dirty,
paused: ctx.paused,
tab_visible: ctx.tab_visible,
denoise_enabled: ctx.denoise_enabled,
remote_active: ctx.remote_active,
remote_accumulator: ctx.remote_accumulator.clone(),
remote_reserved_samples: ctx.remote_reserved_samples,
export_active: ctx.export_active,
live_compute_target: ctx.live_compute_target,
local_compute_target: ctx.local_compute_target,
local_preview_scale: ctx.local_preview_scale,
camera_moving: ctx.camera_moving,
env_map: ctx.env_map.clone(),
}
}
pub fn resolve_material(
materials: &[GemMaterial],
custom_materials: &[GemMaterial],
material_name: &str,
) -> GemMaterial {
custom_materials
.iter()
.find(|m| m.name.eq_ignore_ascii_case(material_name))
.or_else(|| {
materials
.iter()
.find(|m| m.name.eq_ignore_ascii_case(material_name))
})
.cloned()
.unwrap_or_else(|| materials[0].clone())
}
#[derive(Clone, Copy)]
pub struct MaterialOverrides {
pub inclusion_sigma_s: f32,
pub c_axis_override: Option<Vec3>,
pub edge_rounding_radius: f32,
pub stone_width_mm: f32,
}
#[must_use]
pub fn apply_material_overrides(
material: GemMaterial,
overrides: &MaterialOverrides,
active_planes: &[GpuFacetPlane],
width_cache: &mut StoneWidthCache,
) -> GemMaterial {
let material = if overrides.inclusion_sigma_s > 0.0 {
material.with_scattering_amount(overrides.inclusion_sigma_s)
} else {
material
};
let mut material = material;
if let Some(axis) = overrides.c_axis_override
&& material.optical_character != OpticalCharacter::Isotropic
{
material.c_axis = axis;
}
let material = if overrides.edge_rounding_radius > 0.0 {
material.with_edge_rounding(overrides.edge_rounding_radius)
} else {
material
};
if overrides.stone_width_mm > 0.0
&& let Some(model_width) = width_cache.ensure(active_planes)
&& model_width > 1e-9
{
let scale = (f64::from(overrides.stone_width_mm) / model_width) as f32;
if scale.is_finite() && scale > 0.0 {
return material.with_absorption_path_scale(scale);
}
}
material
}
pub(super) fn resolve_material_and_quality(
materials: &[GemMaterial],
custom_materials: &[GemMaterial],
material_name: &str,
target_samples: u32,
overrides: &MaterialOverrides,
active_planes: &[GpuFacetPlane],
width_cache: &mut StoneWidthCache,
) -> (GemMaterial, u32) {
let current_mat = resolve_material(materials, custom_materials, material_name);
let current_mat = apply_material_overrides(current_mat, overrides, active_planes, width_cache);
let spp = (target_samples / 64).clamp(1, 8);
(current_mat, spp)
}