using System.Numerics; using JetBrains.Annotations; using Robust.Shared.GameObjects; using Robust.Shared.IoC; using Robust.Shared.Map; using Robust.Shared.Maths; using Robust.Shared.Physics.Components; using Robust.Shared.Timing; namespace Robust.Shared.Physics.Systems; public abstract partial class SharedPhysicsSystem { [Dependency] private IGameTiming _gameTiming = default!; /// /// Gets the linear velocity of a particular body at the specified point. /// [Pure] [PublicAPI] public Vector2 GetLinearVelocity( EntityUid uid, Vector2 point, PhysicsComponent? component = null, TransformComponent? xform = null) { if (!PhysicsQuery.Resolve(uid, ref component)) return Vector2.Zero; if (!XformQuery.Resolve(uid, ref xform)) return Vector2.Zero; var velocity = component.LinearVelocity; var angVelocity = xform.LocalRotation.RotateVec(Vector2Helpers.Cross(component.AngularVelocity, point - component.LocalCenter)); return velocity + angVelocity; } /// /// This is the total rate of change of the coordinate's map position. /// [Pure] [PublicAPI] public Vector2 GetMapLinearVelocity(EntityCoordinates coordinates) { if (!coordinates.IsValid(EntityManager)) return Vector2.Zero; var mapUid = _transform.GetMap(coordinates); var parent = coordinates.EntityId; var localPos = coordinates.Position; var velocity = Vector2.Zero; var angularComponent = Vector2.Zero; while (parent != mapUid && parent.IsValid()) { // Could make this a method with the below one but ehh // then you get a method bigger than this block with a billion out args and who wants that. var xform = XformQuery.GetComponent(parent); if (PhysicsQuery.TryGetComponent(parent, out var body)) { velocity += body.LinearVelocity; angularComponent += Vector2Helpers.Cross(body.AngularVelocity, localPos - body.LocalCenter); angularComponent = xform.LocalRotation.RotateVec(angularComponent); } localPos = xform.LocalPosition + xform.LocalRotation.RotateVec(localPos); parent = xform.ParentUid; } return velocity; } /// /// This is the total rate of change of the entity's map-position, resulting from the linear and angular /// velocities of this entity and any parents. /// /// /// Use if you need linear and angular at the same time. /// [Pure] [PublicAPI] public Vector2 GetMapLinearVelocity( EntityUid uid, PhysicsComponent? component = null, TransformComponent? xform = null) { if (!XformQuery.Resolve(uid, ref xform)) return Vector2.Zero; PhysicsQuery.Resolve(uid, ref component, false); var parent = xform.ParentUid; var localPos = xform.LocalPosition; var velocity = component?.LinearVelocity ?? Vector2.Zero; Vector2 angularComponent = Vector2.Zero; while (parent != xform.MapUid && parent.IsValid()) { xform = XformQuery.GetComponent(parent); if (PhysicsQuery.TryGetComponent(parent, out var body)) { // add linear velocity of parent relative to it's own parent (again, in map coordinates) velocity += body.LinearVelocity; // add angular velocity that results from the parent's rotation (NOT in map coordinates, but in the parentXform.Parent's frame) angularComponent += Vector2Helpers.Cross(body.AngularVelocity, localPos - body.LocalCenter); angularComponent = xform.LocalRotation.RotateVec(angularComponent); } localPos = xform.LocalPosition + xform.LocalRotation.RotateVec(localPos); parent = xform.ParentUid; } // angular component of the velocity should now be in terms of map coordinates and can be added onto the sum of // linear velocities. return velocity + angularComponent; } /// /// Get the body's total angular velocity. This is the rate of change of the entity's world rotation. /// /// /// Consider using if you need linear and angular at the same time. /// [Pure] [PublicAPI] public float GetMapAngularVelocity( EntityUid uid, PhysicsComponent? component = null, TransformComponent? xform = null) { if (!XformQuery.Resolve(uid, ref xform)) return 0f; PhysicsQuery.Resolve(uid, ref component, false); var angularVelocity = component?.AngularVelocity ?? 0; while (xform.ParentUid != xform.MapUid && xform.ParentUid.IsValid()) { if (PhysicsQuery.TryGetComponent(xform.ParentUid, out var body)) angularVelocity += body.AngularVelocity; xform = XformQuery.GetComponent(xform.ParentUid); } return angularVelocity; } /// /// Gets the linear and angular velocity for this entity in map terms. /// [Pure] [PublicAPI] public (Vector2, float) GetMapVelocities( EntityUid uid, PhysicsComponent? component = null, TransformComponent? xform = null) { if (!XformQuery.Resolve(uid, ref xform)) return (Vector2.Zero, 0); PhysicsQuery.Resolve(uid, ref component, false); var parent = xform.ParentUid; var localPos = xform.LocalPosition; var linearVelocity = component?.LinearVelocity ?? Vector2.Zero; var angularVelocity = component?.AngularVelocity ?? 0; Vector2 linearVelocityAngularContribution = Vector2.Zero; while (parent != xform.MapUid && parent.IsValid()) { xform = XformQuery.GetComponent(parent); if (PhysicsQuery.TryGetComponent(parent, out var body)) { angularVelocity += body.AngularVelocity; // add linear velocity of parent relative to it's own parent (again, in map coordinates) linearVelocity += body.LinearVelocity; // add the component of the linear velocity that results from the parent's rotation. This is NOT in map // coordinates, this is the velocity in the parentXform.Parent's frame. linearVelocityAngularContribution += Vector2Helpers.Cross(body.AngularVelocity, localPos - body.LocalCenter); linearVelocityAngularContribution = xform.LocalRotation.RotateVec(linearVelocityAngularContribution); } localPos = xform.LocalPosition + xform.LocalRotation.RotateVec(localPos); parent = xform.ParentUid; } return (linearVelocity + linearVelocityAngularContribution, angularVelocity); } private void HandleParentChangeVelocity(EntityUid uid, PhysicsComponent physics, EntityUid oldParent, TransformComponent xform) { // If parent changed due to state handling, don't modify velocities. The physics comp state will take care of itself.. if (_gameTiming.ApplyingState) return; if (physics.LifeStage != ComponentLifeStage.Running) return; if (physics.BodyType == BodyType.Static || xform.MapID == MapId.Nullspace || !physics.CanCollide) return; // When transferring bodies, we will preserve map angular and linear velocities. For this purpose, we simply // modify the velocities so that the map value remains unchanged. // TODO currently this causes issues when the parent changes due to teleportation or something like that. Though // I guess the question becomes, what do you do with conservation of momentum in that case. I guess its the job // of the teleporter to select a velocity at the after the parent has changed. FixturesComponent? manager = null; // for the new velocities (that need to be updated), we can just use the existing function: var (newLinear, newAngular) = GetMapVelocities(uid, physics, xform); // for the old velocities, we need to re-implement this function while using the old parent and old local position: if (oldParent == EntityUid.Invalid) { // no previous parent --> simple // Old velocity + (old velocity - new velocity) SetLinearVelocity(uid, physics.LinearVelocity * 2 - newLinear, manager: manager, body: physics); SetAngularVelocity(uid, physics.AngularVelocity * 2 - newAngular, manager: manager, body: physics); return; } var parent = oldParent; TransformComponent? parentXform = XformQuery.GetComponent(parent); var localPos = Vector2.Transform(_transform.GetWorldPosition(xform), _transform.GetInvWorldMatrix(parentXform)); var oldLinear = physics.LinearVelocity; var oldAngular = physics.AngularVelocity; Vector2 linearAngularContribution = Vector2.Zero; do { if (PhysicsQuery.TryGetComponent(parent, out var body)) { oldAngular += body.AngularVelocity; // add linear velocity of parent relative to it's own parent (again, in map coordinates) oldLinear += body.LinearVelocity; // add the component of the linear velocity that results from the parent's rotation. This is NOT in map // coordinates, this is the velocity in the parentXform.Parent's frame. linearAngularContribution += Vector2Helpers.Cross(body.AngularVelocity, localPos - body.LocalCenter); linearAngularContribution = parentXform.LocalRotation.RotateVec(linearAngularContribution); } localPos = parentXform.LocalPosition + parentXform.LocalRotation.RotateVec(localPos); parent = parentXform.ParentUid; } while (parent.IsValid() && XformQuery.TryGetComponent(parent, out parentXform)); oldLinear += linearAngularContribution; // Finally we can update the Velocities. linear velocity is already in terms of map-coordinates, so no // world-rotation is required SetLinearVelocity(uid, physics.LinearVelocity + oldLinear - newLinear, manager: manager, body: physics); SetAngularVelocity(uid, physics.AngularVelocity + oldAngular - newAngular, manager: manager, body: physics); } }