From 71ade52ef032a59f7fc96e236eefa00dbc6285d7 Mon Sep 17 00:00:00 2001 From: ghostly_zsh Date: Tue, 18 Nov 2025 00:43:11 -0600 Subject: [PATCH] even better animation --- crates/unified/src/client/parts.rs | 48 ++++++++++++++++++++---------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/crates/unified/src/client/parts.rs b/crates/unified/src/client/parts.rs index 83121184e7d203a0d22e3ae7fbe9c20d185beb34..0a8763f8d09126397632740bb4424f52f497aea9 100644 --- a/crates/unified/src/client/parts.rs +++ b/crates/unified/src/client/parts.rs @@ -1,3 +1,5 @@ +use std::f32::consts::PI; + use crate::attachment::{Joint, JointOf, Joints, PartInShip, Peer, SnapOf, SnapOfJoint}; use crate::client::Me; use crate::client::colors::GREEN; @@ -115,9 +117,11 @@ struct SnapResource(Option, Option); #[derive(Component)] struct DragGhost; #[derive(Component)] -struct GhostTarget(pub Quat); -#[derive(Component)] -struct GhostVelocity(pub Vec3); +struct Ghost { + pub rot: Quat, + pub last_target_pos: Vec3, + pub vel: Vec3, +} const TRANSLATION_SMOOTH: f32 = 0.3; const ROTATION_SMOOTH: f32 = 0.1; @@ -147,7 +151,11 @@ fn on_part_click( } let mut s = sprite.0.clone(); s.color = Color::srgba(0.7, 0.7, 0.7, 1.0); - commands.spawn((DragGhost, GhostVelocity(Vec3::ZERO), GhostTarget(sprite.1.rotation), *sprite.1, s)); + commands.spawn((DragGhost, Ghost { + rot: sprite.1.rotation, + last_target_pos: sprite.1.translation, + vel: Vec3::ZERO, + }, *sprite.1, s)); drag.0 = Some(ev.event().event_target()); } @@ -191,7 +199,7 @@ fn on_part_release( /// This code is super cursed, and it will break at the lightest breeze fn update_drag_ghosts( ghost: Single< - (&mut Transform, &mut GhostVelocity, &mut GhostTarget), + (&mut Transform, &mut Ghost), ( With, Without, @@ -213,12 +221,12 @@ fn update_drag_ghosts( ) { const CUTOFF: f32 = 25.0; // px - let (mut ghost, mut ghost_velocity, mut ghost_target) = ghost.into_inner(); + let (mut ghost, mut ghost_info) = ghost.into_inner(); let Some(cursor) = cursor.0 else { return }; let mut best_distance = f32::INFINITY; let mut best_target = Transform::from_xyz(cursor.x, cursor.y, 0.0); - best_target.rotation = ghost_target.0; + best_target.rotation = ghost_info.rot; let mut snap = None; let mut best_parent_position = None; let mut best_snap_position = None; @@ -345,14 +353,24 @@ fn update_drag_ghosts( -180.0_f32.to_radians() * time.delta_secs(), )); } - ghost_target.0 = best_target.rotation; - ghost.rotation = ghost.rotation.slerp(ghost_target.0, ROTATION_SMOOTH); - let disp = ghost.translation - best_target.translation; - const K: f32 = 0.07; - const C: f32 = 0.5; - let a = (-K*disp - C*ghost_velocity.0)/2.0; - ghost.translation += ghost_velocity.0; - ghost_velocity.0 += a; + ghost_info.rot = best_target.rotation; + ghost.rotation = ghost.rotation.slerp(ghost_info.rot, ROTATION_SMOOTH); + let target_vel = (best_target.translation - ghost_info.last_target_pos) + /time.delta_secs(); + const F: f32 = 2.0; // frequency (Hz) + // 0.0 is undamped, 0.0-1.0 is underdamped, 1.0 is critical, and >1.0 is over + const ZETA: f32 = 0.7; // damping + const R: f32 = -0.6; // initial response speed + const K1: f32 = ZETA/(PI*F); + const K2: f32 = 1.0/((2.0*PI*F)*(2.0*PI*F)); + const K3: f32 = (R*ZETA)/(2.0*PI*F); + let a = (best_target.translation - ghost.translation + + K3*target_vel - K1*ghost_info.vel)/K2; + + ghost.translation += ghost_info.vel*time.delta_secs(); + ghost_info.vel += a*time.delta_secs(); + + ghost_info.last_target_pos = best_target.translation; rsnap.0 = snap; rsnap.1 = best_self_snap;