aboutsummaryrefslogtreecommitdiff
path: root/crates/fparkan-fx/src/sky.rs
diff options
context:
space:
mode:
authorValentin Popov <valentin@popov.link>2026-10-31 23:26:45 +0300
committerValentin Popov <valentin@popov.link>2026-10-31 23:26:45 +0300
commit8fab4b44b8241ffc6450780c6590908f4406f4da (patch)
treee50d187c1aeb231599c98dc1a827295b943dc706 /crates/fparkan-fx/src/sky.rs
parentd7c18d4c1ab576e4329a85b4729403fef3c09183 (diff)
downloadfparkan-8fab4b44b8241ffc6450780c6590908f4406f4da.tar.xz
fparkan-8fab4b44b8241ffc6450780c6590908f4406f4da.zip
fix(render): animate sky layers on the native UV clock
Diffstat (limited to 'crates/fparkan-fx/src/sky.rs')
-rw-r--r--crates/fparkan-fx/src/sky.rs38
1 files changed, 38 insertions, 0 deletions
diff --git a/crates/fparkan-fx/src/sky.rs b/crates/fparkan-fx/src/sky.rs
index d9ba3ed..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 {
@@ -1787,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");