diff options
Diffstat (limited to 'crates/fparkan-fx/src/sky.rs')
| -rw-r--r-- | crates/fparkan-fx/src/sky.rs | 67 |
1 files changed, 59 insertions, 8 deletions
diff --git a/crates/fparkan-fx/src/sky.rs b/crates/fparkan-fx/src/sky.rs index 26e68ab..1b793f0 100644 --- a/crates/fparkan-fx/src/sky.rs +++ b/crates/fparkan-fx/src/sky.rs @@ -328,6 +328,24 @@ pub struct SkyMesh { } impl SkyMesh { + /// Updates native nebula, star and cloud scroll from the sky object's elapsed milliseconds. + pub fn update_uvs(&mut self, elapsed_ms: u32) { + let offsets = [ + [(elapsed_ms % 300_000) as f32 * 3.333_333_3e-6; 2], + [(elapsed_ms % 100_000) as f32 * 1.0e-5; 2], + [(elapsed_ms % 50_000) as f32 * 2.0e-5, 0.0], + ]; + let inverse_radius = self.radius.recip(); + for vertex in &mut self.vertices { + for stage in 0..3 { + let scale = inverse_radius * SKY_UV_SCALES[stage]; + vertex.uv[stage] = std::array::from_fn(|axis| { + scale * vertex.position[axis] + offsets[stage][axis] + }); + } + } + } + /// Number of triangles in the mesh. #[must_use] pub fn triangle_count(&self) -> usize { @@ -1143,12 +1161,11 @@ impl SunFrame { /// Converts an interpolated atmosphere sun sample to a renderer-facing frame. /// /// The original update computes `orbit_direction = [cos(theta), 0, -/// -sin(theta)]`, then applies the row-vector product of the two birth-angle +/// -sin(theta)]`, then applies the column-vector product of the two birth-angle /// matrices. With `A` and `B` in radians, the resulting direction is: /// /// ```text -/// [x*cos(A), -x*sin(A)*cos(B) + z*sin(B), -/// x*sin(A)*sin(B) + z*cos(B)] +/// [x*cos(A) + z*sin(A)*sin(B), x*sin(A) - z*cos(A)*sin(B), z*cos(B)] /// ``` /// /// where `x` and `z` are the two non-zero orbit components. @@ -1178,9 +1195,9 @@ pub fn sun_frame(sample: &SunSample, now_seconds: f32, config: SunConfig) -> Sun let x = orbit_direction[0]; let z = orbit_direction[2]; let direction = normalize3([ - x * cos_a, - -x * sin_a * cos_b + z * sin_b, - x * sin_a * sin_b + z * cos_b, + x * cos_a + z * sin_a * sin_b, + x * sin_a - z * cos_a * sin_b, + z * cos_b, ]); SunFrame { active: true, @@ -1788,6 +1805,26 @@ mod tests { } #[test] + fn scrolling_uvs_match_native_periods_and_wrapped_elapsed_clock() { + let mut mesh = SkyGeometryConfig::default().try_mesh(&sample()).unwrap(); + let topology = mesh.indices.clone(); + for elapsed in [0, 25_000, 49_999, 50_000, 100_000, 300_000, 4_051_271_258] { + mesh.update_uvs(elapsed); + let zenith = mesh.vertices[0].uv; + assert_eq!(zenith[0], [(elapsed % 300_000) as f32 * 3.333_333_3e-6; 2]); + assert_eq!(zenith[1], [(elapsed % 100_000) as f32 * 1e-5; 2]); + assert_eq!(zenith[2], [(elapsed % 50_000) as f32 * 2e-5, 0.0]); + } + // Live GOG sky render: now 265584022, object origin 509280060. + mesh.update_uvs(265_584_022_u32.wrapping_sub(509_280_060)); + assert!((mesh.vertices[0].uv[2][0] - 0.42516).abs() < 1e-7); + let once = mesh.vertices.clone(); + mesh.update_uvs(4_051_271_258); + assert_eq!(mesh.vertices, once, "updates must not accumulate drift"); + assert_eq!(mesh.indices, topology); + } + + #[test] fn geometry_matches_original_counts_positions_uv_and_winding() { let config = SkyGeometryConfig::new(10.0, std::f32::consts::FRAC_PI_2, 2.0, 4, 3); let mesh = config.try_mesh(&sample()).expect("valid mesh"); @@ -1892,16 +1929,30 @@ mod tests { let sample = sun(true); let at_start = sun_frame(&sample, 0.0, SunConfig::new(0.0, 1.0, [90.0, 0.0])); assert!((at_start.direction[0]).abs() < 1.0e-5); - assert!((at_start.direction[1] + at_start.orbit_direction[0]).abs() < 1.0e-5); + assert!((at_start.direction[1] - at_start.orbit_direction[0]).abs() < 1.0e-5); assert!((at_start.direction[2] - at_start.orbit_direction[2]).abs() < 1.0e-5); let side = sun_frame(&sample, 0.0, SunConfig::new(0.0, 1.0, [0.0, 90.0])); assert!((side.direction[0] - side.orbit_direction[0]).abs() < 1.0e-5); - assert!((side.direction[1] - side.orbit_direction[2]).abs() < 1.0e-5); + assert!((side.direction[1] + side.orbit_direction[2]).abs() < 1.0e-5); assert!(side.direction[2].abs() < 1.0e-5); } #[test] + fn sun_direction_matches_live_native_birth_matrix() { + // Terrain CSun::Update, elapsed 282184 ms, lifetime 525000 ms. + let frame = sun_frame( + &sun(true), + 282.184, + SunConfig::new(0.0, 525.0, [90.0, 30.0]), + ); + let expected = [0xbefd_7269, 0xbe10_4168, 0xbf5b_7dda].map(f32::from_bits); + for (actual, expected) in frame.direction.into_iter().zip(expected) { + assert!((actual - expected).abs() < 3.0e-6, "{actual} != {expected}"); + } + } + + #[test] fn native_birth_keeps_the_real_start_time() { assert!(!celestial_frame(&sun(false), 100.0).active); let birth = SunBirth { |
