diff --git a/crates/polymodel-renderer-worker/src/lib.rs b/crates/polymodel-renderer-worker/src/lib.rs index abcc752..f3fd4cc 100644 --- a/crates/polymodel-renderer-worker/src/lib.rs +++ b/crates/polymodel-renderer-worker/src/lib.rs @@ -1158,7 +1158,7 @@ pub fn emit_lighting_diagnostic(metadata: &NativeCaptureMetadata, scene: &Scene) } } -pub const PM90_FIXTURE: &[u8] = b"0 PM-90 lighting fixture v1\\n0 BFC CERTIFY CCW\\n3 4 0 0 0 1 0 0 0 1 0\\n3 14 0 0 1 1 0 1 0 1 1\\n0 BFC CERTIFY CW\\n0 BFC INVERTNEXT\\n3 4 0 0 -1 1 0 -1 0 1 -1\\n"; +pub const PM90_FIXTURE: &[u8] = b"0 PM-90 lighting fixture v1\n0 BFC CERTIFY CCW\n3 4 0 0 0 1 0 0 0 1 0\n3 14 0 0 1 1 0 1 0 1 1\n0 BFC CERTIFY CW\n0 BFC INVERTNEXT\n3 4 0 0 -1 1 0 -1 0 1 -1\n"; pub fn pm90_fixture_hash() -> String { use sha2::{Digest, Sha256}; @@ -2753,6 +2753,55 @@ fn scene_instance_batches_for_mode( Ok(batches) } +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct LdrawLightingMaterialPolicy { + pub version: &'static str, + pub ambient_energy: f32, + pub ambient_colour: [u8; 3], + pub key_energy: f32, + pub key_colour: [u8; 3], + pub key_direction: [f32; 3], + pub fill_energy: f32, + pub fill_colour: [u8; 3], + pub fill_direction: [f32; 3], + pub albedo_is_direct_colour: bool, + pub roughness: f32, + pub metallic: f32, + pub cull_back_faces: bool, + pub depth_test: bool, + pub depth_write: bool, + pub alpha_blend: bool, +} + +impl LdrawLightingMaterialPolicy { + pub const VERSION: &'static str = "pm-ldraw-lighting-material-v1"; + + pub const fn ldraw() -> Self { + Self { + version: Self::VERSION, + ambient_energy: 0.45, + ambient_colour: [255, 255, 255], + key_energy: 2.6, + key_colour: [255, 255, 255], + key_direction: [-0.45, -0.55, -0.70], + fill_energy: 0.8, + fill_colour: [210, 225, 255], + fill_direction: [0.55, 0.35, -0.30], + albedo_is_direct_colour: true, + roughness: 0.72, + metallic: 0.0, + cull_back_faces: true, + depth_test: true, + depth_write: true, + alpha_blend: false, + } + } + + pub const fn light_facing_vector(direction: [f32; 3]) -> [f32; 3] { + [-direction[0], -direction[1], -direction[2]] + } +} + #[cfg(not(target_arch = "wasm32"))] pub struct NativeLights { ambient: AmbientLight, @@ -2763,19 +2812,44 @@ pub struct NativeLights { #[cfg(not(target_arch = "wasm32"))] impl NativeLights { pub fn new(context: &three_d::Context) -> Self { + let policy = LdrawLightingMaterialPolicy::ldraw(); Self { - ambient: AmbientLight::new(context, 0.45, Srgba::WHITE), + ambient: AmbientLight::new( + context, + policy.ambient_energy, + Srgba::new_opaque( + policy.ambient_colour[0], + policy.ambient_colour[1], + policy.ambient_colour[2], + ), + ), key: DirectionalLight::new( context, - 2.6, - Srgba::WHITE, - three_d::vec3(-0.45, -0.55, -0.70), + policy.key_energy, + Srgba::new_opaque( + policy.key_colour[0], + policy.key_colour[1], + policy.key_colour[2], + ), + three_d::vec3( + policy.key_direction[0], + policy.key_direction[1], + policy.key_direction[2], + ), ), fill: DirectionalLight::new( context, - 0.8, - Srgba::new_opaque(210, 225, 255), - three_d::vec3(0.55, 0.35, -0.30), + policy.fill_energy, + Srgba::new_opaque( + policy.fill_colour[0], + policy.fill_colour[1], + policy.fill_colour[2], + ), + three_d::vec3( + policy.fill_direction[0], + policy.fill_direction[1], + policy.fill_direction[2], + ), ), } } @@ -3449,17 +3523,18 @@ fn build_scene_model_from_batches( .map(|instance| scene_transform_to_mat4(instance.transform).map_err(str::to_owned)) .collect::, _>>()?; let colour = index.material(scene, batch.material)?.colour; + let policy = LdrawLightingMaterialPolicy::ldraw(); let mut material = PhysicalMaterial::new_opaque( context, &CpuMaterial { albedo: ldraw_colour(colour), - roughness: 0.72, - metallic: 0.0, + roughness: policy.roughness, + metallic: policy.metallic, ..Default::default() }, ); material.render_states = RenderStates { - cull: if batch.bfc_cull { + cull: if batch.bfc_cull && policy.cull_back_faces { Cull::Back } else { Cull::None @@ -4574,6 +4649,61 @@ mod tests { assert_eq!(pm90_fixture_hash().len(), 64); } + #[test] + fn ldraw_policy_matches_three_d_direction_and_material_contract() { + let policy = LdrawLightingMaterialPolicy::ldraw(); + assert_eq!(policy.version, LdrawLightingMaterialPolicy::VERSION); + assert_eq!(policy.ambient_energy, 0.45); + assert_eq!(policy.ambient_colour, [255, 255, 255]); + assert_eq!(policy.key_energy, 2.6); + assert_eq!(policy.key_colour, [255, 255, 255]); + assert_eq!(policy.key_direction, [-0.45, -0.55, -0.70]); + assert_eq!(policy.fill_energy, 0.8); + assert_eq!(policy.fill_colour, [210, 225, 255]); + assert_eq!(policy.fill_direction, [0.55, 0.35, -0.30]); + assert_eq!( + LdrawLightingMaterialPolicy::light_facing_vector(policy.key_direction), + [0.45, 0.55, 0.70] + ); + assert_eq!( + LdrawLightingMaterialPolicy::light_facing_vector(policy.fill_direction), + [-0.55, -0.35, 0.30] + ); + assert!(policy.albedo_is_direct_colour); + assert_eq!(policy.roughness, 0.72); + assert_eq!(policy.metallic, 0.0); + assert!(policy.cull_back_faces); + assert!(policy.depth_test); + assert!(policy.depth_write); + assert!(!policy.alpha_blend); + } + + #[cfg(target_arch = "wasm32")] + mod worker_policy_tests { + use super::LdrawLightingMaterialPolicy; + + #[test] + fn worker_target_uses_shared_ldraw_policy() { + let policy = LdrawLightingMaterialPolicy::ldraw(); + assert_eq!(policy.version, "pm-ldraw-lighting-material-v1"); + assert_eq!(policy.ambient_energy, 0.45); + assert_eq!(policy.key_energy, 2.6); + assert_eq!(policy.fill_energy, 0.8); + assert_eq!(policy.key_direction, [-0.45, -0.55, -0.70]); + assert_eq!(policy.fill_direction, [0.55, 0.35, -0.30]); + assert_eq!( + LdrawLightingMaterialPolicy::light_facing_vector(policy.key_direction), + [0.45, 0.55, 0.70] + ); + assert_eq!(policy.fill_colour, [210, 225, 255]); + assert_eq!(policy.roughness, 0.72); + assert_eq!(policy.metallic, 0.0); + assert!(policy.albedo_is_direct_colour); + assert!(policy.cull_back_faces && policy.depth_test && policy.depth_write); + assert!(!policy.alpha_blend); + } + } + #[cfg(not(target_arch = "wasm32"))] #[test] fn native_case_pose_changes_front_back_and_preserves_fit_defaults() { diff --git a/crates/polymodel-renderer-worker/src/worker.rs b/crates/polymodel-renderer-worker/src/worker.rs index 80c42f4..c1d3855 100644 --- a/crates/polymodel-renderer-worker/src/worker.rs +++ b/crates/polymodel-renderer-worker/src/worker.rs @@ -1018,19 +1018,43 @@ fn standard_lights( three_d::DirectionalLight, three_d::DirectionalLight, ) { - let ambient_light = - three_d::AmbientLight::new(context, 1.15, three_d::Srgba::new_opaque(255, 255, 255)); + let policy = polymodel_renderer_worker::LdrawLightingMaterialPolicy::ldraw(); + let ambient_light = three_d::AmbientLight::new( + context, + policy.ambient_energy, + three_d::Srgba::new_opaque( + policy.ambient_colour[0], + policy.ambient_colour[1], + policy.ambient_colour[2], + ), + ); let key_light = three_d::DirectionalLight::new( context, - 3.4, - three_d::Srgba::new_opaque(255, 255, 255), - three_d::vec3(-0.45, -0.55, -0.70), + policy.key_energy, + three_d::Srgba::new_opaque( + policy.key_colour[0], + policy.key_colour[1], + policy.key_colour[2], + ), + three_d::vec3( + policy.key_direction[0], + policy.key_direction[1], + policy.key_direction[2], + ), ); let fill_light = three_d::DirectionalLight::new( context, - 1.7, - three_d::Srgba::new_opaque(215, 230, 255), - three_d::vec3(0.55, 0.35, -0.35), + policy.fill_energy, + three_d::Srgba::new_opaque( + policy.fill_colour[0], + policy.fill_colour[1], + policy.fill_colour[2], + ), + three_d::vec3( + policy.fill_direction[0], + policy.fill_direction[1], + policy.fill_direction[2], + ), ); (ambient_light, key_light, fill_light) }