using Sandbox; using System; using System.Numerics; namespace VeloX; public static class PhysicsExtensions { public static Vector3 Transform( this Vector3 value, Quaternion rotation ) { float x2 = rotation.X + rotation.X; float y2 = rotation.Y + rotation.Y; float z2 = rotation.Z + rotation.Z; float wx2 = rotation.W * x2; float wy2 = rotation.W * y2; float wz2 = rotation.W * z2; float xx2 = rotation.X * x2; float xy2 = rotation.X * y2; float xz2 = rotation.X * z2; float yy2 = rotation.Y * y2; float yz2 = rotation.Y * z2; float zz2 = rotation.Z * z2; return new Vector3( value.x * (1.0f - yy2 - zz2) + value.y * (xy2 - wz2) + value.z * (xz2 + wy2), value.x * (xy2 + wz2) + value.y * (1.0f - xx2 - zz2) + value.z * (yz2 - wx2), value.x * (xz2 - wy2) + value.y * (yz2 + wx2) + value.z * (1.0f - xx2 - yy2) ); } /// /// Calculates the linear and angular velocities on the center of mass for an offset impulse. /// /// The physics object /// The impulse acting on the object in kg*units/s (World frame) /// The location of the impulse in world coordinates /// /// Vector1: Linear velocity from the impulse (World frame) /// Vector2: Angular velocity from the impulse (Local frame) /// public static (Vector3 LinearVelocity, Vector3 AngularVelocity) CalculateVelocityOffset( this PhysicsBody physObj, Vector3 impulse, Vector3 position ) { if ( !physObj.IsValid() || !physObj.MotionEnabled ) return (Vector3.Zero, Vector3.Zero); Vector3 linearVelocity = impulse / physObj.Mass; Vector3 r = position - physObj.MassCenter; // Calculate torque impulse in world frame: τ = r × impulse Vector3 torqueImpulseWorld = r.Cross( impulse ); Rotation worldToLocal = physObj.Rotation.Inverse; Vector3 torqueImpulseLocal = torqueImpulseWorld.Transform( worldToLocal ); var InverseInertiaDiagLocal = physObj.Inertia.Inverse; // Compute angular velocity change in rad/s (local frame) Vector3 angularVelocityRadLocal = new( InverseInertiaDiagLocal.x * torqueImpulseLocal.x, InverseInertiaDiagLocal.y * torqueImpulseLocal.y, InverseInertiaDiagLocal.z * torqueImpulseLocal.z ); const float radToDeg = 180f / MathF.PI; Vector3 angularVelocityDegLocal = angularVelocityRadLocal * radToDeg; return (linearVelocity, angularVelocityDegLocal); } /// /// Calculates the linear and angular impulses on the object's center of mass for an offset impulse. /// /// The physics object /// The impulse acting on the object in kg*units/s (World frame) /// The location of the impulse in world coordinates /// /// Vector1: Linear impulse on center of mass (World frame) /// Vector2: Angular impulse on center of mass (Local frame) /// public static (Vector3 LinearImpulse, Vector3 AngularImpulse) CalculateForceOffset( this PhysicsBody physObj, Vector3 impulse, Vector3 position ) { if ( !physObj.IsValid() || !physObj.MotionEnabled ) { return (Vector3.Zero, Vector3.Zero); } // 1. Linear impulse is the same as the input impulse (conservation of momentum) Vector3 linearImpulse = impulse; // 2. Calculate angular impulse (torque) from the offset force // τ = r * F (cross product of position relative to COM and force) Vector3 centerOfMass = physObj.MassCenter; Vector3 relativePosition = position - centerOfMass; Vector3 worldAngularImpulse = relativePosition.Cross( impulse ); // Convert angular impulse to local space (since we'll use it with LocalInertia) Rotation bodyRotation = physObj.Transform.Rotation; Vector3 localAngularImpulse = bodyRotation.Inverse * worldAngularImpulse; return (linearImpulse, localAngularImpulse); } }