From 8d2bec1c927f2adeffd7fdf0b14c7d29db41df23 Mon Sep 17 00:00:00 2001 From: ghostly_zsh Date: Fri, 12 Jun 2026 20:03:35 -0500 Subject: [PATCH] netcode: rotation interpolation --- crates/unified/assets/config/world.wc.toml | 2 +- crates/unified/src/client/interpolation.rs | 75 +++++++++++-------- crates/unified/src/client/parts.rs | 6 +- .../src/client/planet/incoming_planets.rs | 6 +- 4 files changed, 46 insertions(+), 43 deletions(-) diff --git a/crates/unified/assets/config/world.wc.toml b/crates/unified/assets/config/world.wc.toml index 5c7798d842fd39ee656a7a74a028c168fb41e8d4..f4711b8c546b7dc2d210d4f88d9b9c388ceb0f5c 100644 --- a/crates/unified/assets/config/world.wc.toml +++ b/crates/unified/assets/config/world.wc.toml @@ -3,7 +3,7 @@ gravity = 500.0 gravity_iterations = 8 spawn_parts_at = "Earth" -spawn_parts_interval_secs = 1000 +spawn_parts_interval_secs = 1 orbit_scale_factor = 4.0 [part] diff --git a/crates/unified/src/client/interpolation.rs b/crates/unified/src/client/interpolation.rs index 11359cb1edd8168dbce60ae86aa46b7c4f12b11c..46709cc6a748d065df89032b06891a7202573c03 100644 --- a/crates/unified/src/client/interpolation.rs +++ b/crates/unified/src/client/interpolation.rs @@ -1,5 +1,5 @@ use std::collections::VecDeque; -use std::f32::consts::PI; +use std::f64::consts::PI; use std::time::{Duration}; use avian2d::parry::transformation::utils::transform; use bevy_replicon::client::confirm_history::ConfirmHistory; @@ -23,23 +23,19 @@ pub struct TranslationInterpolationInfo { } #[derive(Component, Debug)] pub struct RotationInterpolationInfo { - pub last_dt: Duration, - pub this_tick_start: bevy::platform::time::Instant, - pub last_angular_velocity: f32, - pub latest_rotation: f32, - pub last_rotation: f32, + pub rotations: VecDeque<(f64, f64)> // time and rotation } fn update_interpolation_info( mut interpolation_pos_query: Query<(Entity, &Position, &mut TranslationInterpolationInfo, &ConfirmHistory), Changed>, - mut interpolation_rot_query: Query<(&Rotation, &AngularVelocity, &mut RotationInterpolationInfo), Changed>, + mut interpolation_rot_query: Query<(&Rotation, &AngularVelocity, &mut RotationInterpolationInfo, &ConfirmHistory), Changed>, server_clock: Res, server_time_offset: Res, ) { for (entity, position, mut info, confirm_history) in interpolation_pos_query.iter_mut() { - let now = confirm_history.last_tick().get() as f64 / TICK_RATE - **server_time_offset/* - server_clock.rtt/2.0 - server_clock.time_offset*/; - //let now = confirm_history.last_tick().get() as f32 / TICK_RATE as f32; + let now = confirm_history.last_tick().get() as f64 / TICK_RATE - **server_time_offset; info.positions.push_back((now, position.as_vec2())); + let mut last_over_time = 0; for (i, (time, _)) in info.positions.iter().enumerate() { if *time < now - INTERP { @@ -47,31 +43,33 @@ fn update_interpolation_info( } } if last_over_time > 0 { - //debug!("pop"); info.positions.drain(..last_over_time); } - //debug!("{:?}", info.positions) - /*if let Some((time, _)) = info.positions.get(0) && now.duration_since(*time) > INTERP { - debug!("pop"); - pos_info.positions.pop_front(); - }*/ } - for (rotation, angular_velocity, mut info) in interpolation_rot_query.iter_mut() { - info.last_dt = info.this_tick_start.elapsed(); - info.this_tick_start = bevy::platform::time::Instant::now(); - let avg_angular_velocity = (angular_velocity.0.val_num_f32() + info.last_angular_velocity) / 2.0; - let delta_rotation = avg_angular_velocity * info.last_dt.as_secs_f32(); + for (rotation, angular_velocity, mut info, confirm_history) in interpolation_rot_query.iter_mut() { + let now = confirm_history.last_tick().get() as f64 / TICK_RATE - **server_time_offset; + let delta_rotation = angular_velocity.0.to_radians() / TICK_RATE; let num_revolutions = (delta_rotation / (2.0*PI)).round(); - info.last_rotation = info.latest_rotation; let mut rotation_offset = 0.0; - let rotation = rotation.as_radians().val_num_f32(); - if delta_rotation + info.last_rotation >= PI { - rotation_offset = 2.0*PI; - } else if delta_rotation + info.last_rotation < -PI { - rotation_offset = -2.0*PI; + let rotation = rotation.as_radians() as f64; + if let Some((_, rotation)) = info.rotations.back() { + if delta_rotation + rotation >= PI { + rotation_offset = 2.0 * PI; + } else if delta_rotation + rotation < -PI { + rotation_offset = -2.0 * PI; + } + } + info.rotations.push_back((now, rotation + rotation_offset + num_revolutions*2.0*PI)); + + let mut last_over_time = 0; + for (i, (time, _)) in info.rotations.iter().enumerate() { + if *time < now - INTERP { + last_over_time = i; + } + } + if last_over_time > 0 { + info.rotations.drain(..last_over_time); } - info.latest_rotation = rotation + rotation_offset + num_revolutions*2.0*PI; - info.last_angular_velocity = angular_velocity.0.val_num_f32(); } } fn sync_non_interpolated_transforms( @@ -104,11 +102,24 @@ fn do_interpolation( let dt = second_time.0 - first_time.0; let progress = elapsed / dt; // should be between 0.0 and 1.0 if progress < 0.0 || progress > 1.0 { continue } - //debug!("{:?} {:?} {:?} {}", entity, first_time.1, second_time.1, progress); transform.translation = (first_time.1 + progress as f32 * (second_time.1 - first_time.1)).extend(0.0); - let dt = bevy::platform::time::Instant::now() - rot_info.this_tick_start; - let progress = dt.as_secs_f32() / rot_info.last_dt.as_secs_f32(); // should be between 0.0 and 1.0 - transform.rotation = Quat::from_rotation_z(rot_info.last_rotation + progress * (rot_info.latest_rotation - rot_info.last_rotation)); + let mut time_after_now = 0; + for (i, (time, _)) in rot_info.rotations.iter().enumerate() { + if *time > now - INTERP { + time_after_now = i; + break; + } + } + // this should not happen, but is necessary to prevent a panic + if time_after_now == 0 { continue } + // we need to have 2 packets to reference, so continuing if we don't have that is a-ok + let Some(first_time) = rot_info.rotations.get(time_after_now-1) else { continue }; + let Some(second_time) = rot_info.rotations.get(time_after_now) else { continue }; + let elapsed = now - INTERP - first_time.0; + let dt = second_time.0 - first_time.0; + let progress = elapsed / dt; // should be between 0.0 and 1.0 + if progress < 0.0 || progress > 1.0 { continue } + transform.rotation = Quat::from_rotation_z((first_time.1 + progress * (second_time.1 - first_time.1)) as f32); } } \ No newline at end of file diff --git a/crates/unified/src/client/parts.rs b/crates/unified/src/client/parts.rs index 34ab3479fd5f6e2e4ad297faa6e9b6b062b51f4f..380496ce87ebfa3f16d1356920fb3f5ac7eeae30 100644 --- a/crates/unified/src/client/parts.rs +++ b/crates/unified/src/client/parts.rs @@ -65,11 +65,7 @@ fn handle_incoming_parts( positions: VecDeque::new(), }) .insert(RotationInterpolationInfo { - last_dt: Duration::from_millis(50), // assume it was 20tps because we don't know - this_tick_start: bevy::platform::time::Instant::now(), - last_angular_velocity: 0.0, - latest_rotation: transform.rotation.to_euler(EulerRot::XYZ).2, - last_rotation: transform.rotation.to_euler(EulerRot::XYZ).2, + rotations: VecDeque::new(), }) .observe(on_part_click) .observe(open_crafting_ui); diff --git a/crates/unified/src/client/planet/incoming_planets.rs b/crates/unified/src/client/planet/incoming_planets.rs index 2f76f47c5ab70239008b2f480df48e85d8e96994..2536f924e7ad2e951f2bba7e1cc64dfa68aa7e4b 100644 --- a/crates/unified/src/client/planet/incoming_planets.rs +++ b/crates/unified/src/client/planet/incoming_planets.rs @@ -32,11 +32,7 @@ fn handle_incoming_planets( positions: VecDeque::new(), }) .insert(RotationInterpolationInfo { - last_dt: Duration::from_millis(50), // assume it was 20tps because we don't know - this_tick_start: bevy::platform::time::Instant::now(), - last_angular_velocity: 0.0, - latest_rotation: transform.rotation.to_euler(EulerRot::XYZ).2, - last_rotation: transform.rotation.to_euler(EulerRot::XYZ).2, + rotations: VecDeque::new(), }); trace!(?new_planet, "prepared new planet"); }