use super::RenderContext;
use crate::settings::model::Backdrop;
use glam::Vec3;
use indicatrix::{
geometry::{plane::GpuFacetPlane, tool::ToolPrimitive},
optics::{fluorescence::Fluorescence, materials::GemMaterial, raytracer::LightingPreset},
renderer::env_map::EnvironmentMap,
};
use std::sync::Arc;
struct SceneKey {
dims: [u32; 4],
floats: [u32; 11],
c_axis: Option<[u32; 3]>,
girdle_frosted: bool,
lighting_preset: LightingPreset,
backdrop: Backdrop,
material_name: String,
material_override: Option<GemMaterial>,
view_color: Option<[u32; 3]>,
active_planes: Arc<Vec<GpuFacetPlane>>,
active_tools: Arc<Vec<ToolPrimitive>>,
custom_materials: Arc<Vec<GemMaterial>>,
fluorescence: Option<Arc<Fluorescence>>,
env_map: Option<Arc<EnvironmentMap>>,
}
const fn dims(ctx: &RenderContext) -> [u32; 4] {
[ctx.width, ctx.height, ctx.target_samples, ctx.max_bounces]
}
const fn floats(ctx: &RenderContext) -> [u32; 11] {
[
ctx.yaw.to_bits(),
ctx.pitch.to_bits(),
ctx.distance.to_bits(),
ctx.light_yaw.to_bits(),
ctx.light_pitch.to_bits(),
ctx.exposure.to_bits(),
ctx.inclusion_sigma_s.to_bits(),
ctx.edge_rounding_radius.to_bits(),
ctx.stone_width_mm.to_bits(),
ctx.surface_glare.to_bits(),
ctx.head_shadow_deg.to_bits(),
]
}
fn c_axis(ctx: &RenderContext) -> Option<[u32; 3]> {
ctx.c_axis_override
.map(|v: Vec3| [v.x.to_bits(), v.y.to_bits(), v.z.to_bits()])
}
fn view_color_bits(ctx: &RenderContext) -> Option<[u32; 3]> {
ctx.effective_view_body_color()
.map(|rgb| rgb.map(f32::to_bits))
}
fn same_fluorescence(a: Option<&Arc<Fluorescence>>, b: Option<&Arc<Fluorescence>>) -> bool {
match (a, b) {
(None, None) => true,
(Some(a), Some(b)) => Arc::ptr_eq(a, b),
_ => false,
}
}
fn same_env(a: Option<&Arc<EnvironmentMap>>, b: Option<&Arc<EnvironmentMap>>) -> bool {
match (a, b) {
(None, None) => true,
(Some(a), Some(b)) => Arc::ptr_eq(a, b),
_ => false,
}
}
impl SceneKey {
fn of(ctx: &RenderContext) -> Self {
Self {
dims: dims(ctx),
floats: floats(ctx),
c_axis: c_axis(ctx),
girdle_frosted: ctx.girdle_frosted,
lighting_preset: ctx.lighting_preset,
backdrop: ctx.backdrop,
material_name: ctx.material_name.clone(),
material_override: ctx.material_override.clone(),
view_color: view_color_bits(ctx),
active_planes: Arc::clone(&ctx.active_planes),
active_tools: Arc::clone(&ctx.active_tools),
custom_materials: Arc::clone(&ctx.custom_materials),
fluorescence: ctx.active_fluorescence(),
env_map: ctx.env_map.clone(),
}
}
fn matches(&self, ctx: &RenderContext) -> bool {
self.dims == dims(ctx)
&& self.floats == floats(ctx)
&& self.c_axis == c_axis(ctx)
&& self.girdle_frosted == ctx.girdle_frosted
&& self.lighting_preset == ctx.lighting_preset
&& self.backdrop == ctx.backdrop
&& self.material_name == ctx.material_name
&& self.material_override == ctx.material_override
&& self.view_color == view_color_bits(ctx)
&& Arc::ptr_eq(&self.active_planes, &ctx.active_planes)
&& Arc::ptr_eq(&self.active_tools, &ctx.active_tools)
&& Arc::ptr_eq(&self.custom_materials, &ctx.custom_materials)
&& same_fluorescence(
self.fluorescence.as_ref(),
ctx.active_fluorescence().as_ref(),
)
&& same_env(self.env_map.as_ref(), ctx.env_map.as_ref())
}
}
#[derive(Default)]
pub struct SceneIdentity {
key: Option<SceneKey>,
generation: u64,
}
impl RenderContext {
pub fn scene_generation(&mut self) -> u64 {
let unchanged = self
.scene_identity
.key
.as_ref()
.is_some_and(|key| key.matches(self));
if !unchanged {
self.scene_identity.key = Some(SceneKey::of(self));
self.scene_identity.generation += 1;
}
self.scene_identity.generation
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_unchanged_scene_keeps_its_generation() {
let mut ctx = RenderContext::default();
let first = ctx.scene_generation();
assert_eq!(ctx.scene_generation(), first);
ctx.tab_visible = false;
ctx.paused = true;
ctx.dirty = true;
ctx.camera_drag_held = true;
assert_eq!(ctx.scene_generation(), first);
}
#[test]
fn surface_glare_restarts_like_a_lighting_change_and_one_is_the_default() {
let mut ctx = RenderContext::default();
assert_eq!(ctx.surface_glare.to_bits(), 1.0f32.to_bits());
let first = ctx.scene_generation();
ctx.surface_glare = 1.0;
assert_eq!(ctx.scene_generation(), first);
ctx.surface_glare = 0.0;
let second = ctx.scene_generation();
assert!(second > first, "a glare change must bump the generation");
ctx.lighting_preset = LightingPreset::Daylight;
assert!(
ctx.scene_generation() > second,
"a lighting change bumps it the same way"
);
}
#[test]
fn every_kind_of_scene_change_bumps_the_generation() {
let mut ctx = RenderContext::default();
let mut last = ctx.scene_generation();
let changes: [fn(&mut RenderContext); 11] = [
|c| c.yaw += 0.1,
|c| c.exposure = 2.0,
|c| c.material_name = "Spinel".to_string(),
|c| c.material_override = Some(GemMaterial::diamond()),
|c| c.active_planes = Arc::new(c.active_planes.as_ref().clone()),
|c| {
c.active_tools = Arc::new(vec![ToolPrimitive::ball(
glam::Vec3::new(0.0, 0.3, 0.0),
0.1,
)]);
},
|c| c.env_map = Some(Arc::new(EnvironmentMap::uniform(2, 2, [1.0, 1.0, 1.0]))),
|c| c.girdle_frosted = true,
|c| c.width = 640,
|c| c.surface_glare = 0.5,
|c| c.head_shadow_deg = 0.0,
];
for change in changes {
change(&mut ctx);
let now = ctx.scene_generation();
assert!(now > last, "a scene change must bump the generation");
last = now;
}
}
}