mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 03:30:53 +01:00
Rewrite the physics engine (#1037)
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Interfaces.Physics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Client.GameObjects
|
||||
@@ -9,10 +12,13 @@ namespace Robust.Client.GameObjects
|
||||
/// in the physics system as a dynamic ridged body object that has physics. This behavior overrides
|
||||
/// the BoundingBoxComponent behavior of making the entity static.
|
||||
/// </summary>
|
||||
internal class PhysicsComponent : Component
|
||||
public class PhysicsComponent : SharedPhysicsComponent
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Physics";
|
||||
private Vector2 _linVel;
|
||||
private float _angVel;
|
||||
private float _mass;
|
||||
private VirtualController _controller;
|
||||
private BodyStatus _status;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override uint? NetID => NetIDs.PHYSICS;
|
||||
@@ -21,13 +27,65 @@ namespace Robust.Client.GameObjects
|
||||
/// Current mass of the entity in kg.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public float Mass { get; private set; }
|
||||
public override float Mass
|
||||
{
|
||||
get => _mass;
|
||||
set => _mass = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current velocity of the entity.
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public Vector2 Velocity { get; private set; }
|
||||
public override Vector2 LinearVelocity
|
||||
{
|
||||
get => _linVel;
|
||||
set => _linVel = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current angular velocity of the entity
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public override float AngularVelocity
|
||||
{
|
||||
get => _angVel;
|
||||
set => _angVel = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current momentum of the entity
|
||||
/// </summary>
|
||||
[ViewVariables]
|
||||
public override Vector2 Momentum
|
||||
{
|
||||
get => LinearVelocity * Mass;
|
||||
set => _linVel = value / Mass;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current status of the object
|
||||
/// </summary>
|
||||
public override BodyStatus Status
|
||||
{
|
||||
get => _status;
|
||||
set => _status = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this component is on the ground
|
||||
/// </summary>
|
||||
public override bool OnGround => Status == BodyStatus.OnGround &&
|
||||
!IoCManager.Resolve<IPhysicsManager>()
|
||||
.IsWeightless(Owner.Transform.GridPosition);
|
||||
|
||||
/// <summary>
|
||||
/// Represents a virtual controller acting on the physics component.
|
||||
/// </summary>
|
||||
public override VirtualController Controller
|
||||
{
|
||||
get => _controller;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void HandleComponentState(ComponentState curState, ComponentState nextState)
|
||||
@@ -37,7 +95,8 @@ namespace Robust.Client.GameObjects
|
||||
|
||||
var newState = (PhysicsComponentState)curState;
|
||||
Mass = newState.Mass / 1000f; // gram to kilogram
|
||||
Velocity = newState.Velocity;
|
||||
LinearVelocity = newState.LinearVelocity;
|
||||
AngularVelocity = newState.AngularVelocity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
25
Robust.Client/Physics/PhysicsSystem.cs
Normal file
25
Robust.Client/Physics/PhysicsSystem.cs
Normal file
@@ -0,0 +1,25 @@
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.GameObjects;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
|
||||
namespace Robust.Client.Physics
|
||||
{
|
||||
[UsedImplicitly]
|
||||
public class PhysicsSystem: EntitySystem
|
||||
{
|
||||
private float _lastServerMsg;
|
||||
|
||||
public override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
EntityQuery = new TypeEntityQuery<PhysicsComponent>();
|
||||
}
|
||||
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -199,7 +199,7 @@ namespace Robust.Client.Placement
|
||||
bounds.Width,
|
||||
bounds.Height);
|
||||
|
||||
if (pManager.PhysicsManager.IsColliding(collisionbox, pManager.MapManager.GetGrid(coordinates.GridID).ParentMapId))
|
||||
if (pManager.PhysicsManager.TryCollideRect(collisionbox, pManager.MapManager.GetGrid(coordinates.GridID).ParentMapId))
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
||||
@@ -25,10 +25,10 @@ namespace Robust.Server.Debugging
|
||||
{
|
||||
var msg = _net.CreateNetMessage<MsgRay>();
|
||||
msg.RayOrigin = data.Ray.Position;
|
||||
if (data.Results.DidHitObject)
|
||||
if (data.Results != null)
|
||||
{
|
||||
msg.DidHit = true;
|
||||
msg.RayHit = data.Results.HitPos;
|
||||
msg.RayHit = data.Results.Value.HitPos;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Network;
|
||||
using Robust.Shared.Interfaces.Physics;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
@@ -16,11 +14,13 @@ namespace Robust.Server.GameObjects
|
||||
/// in the physics system as a dynamic ridged body object that has physics. This behavior overrides
|
||||
/// the BoundingBoxComponent behavior of making the entity static.
|
||||
/// </summary>
|
||||
public class PhysicsComponent : Component, ICollideSpecial
|
||||
public class PhysicsComponent : SharedPhysicsComponent
|
||||
{
|
||||
private float _mass;
|
||||
private Vector2 _linVelocity;
|
||||
private float _angVelocity;
|
||||
private VirtualController _controller = null;
|
||||
private BodyStatus _status;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Physics";
|
||||
@@ -32,7 +32,7 @@ namespace Robust.Server.GameObjects
|
||||
/// Current mass of the entity in kilograms.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Mass
|
||||
public override float Mass
|
||||
{
|
||||
get => _mass;
|
||||
set
|
||||
@@ -46,7 +46,7 @@ namespace Robust.Server.GameObjects
|
||||
/// Current linear velocity of the entity in meters per second.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public Vector2 LinearVelocity
|
||||
public override Vector2 LinearVelocity
|
||||
{
|
||||
get => _linVelocity;
|
||||
set
|
||||
@@ -63,7 +63,7 @@ namespace Robust.Server.GameObjects
|
||||
/// Current angular velocity of the entity in radians per sec.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float AngularVelocity
|
||||
public override float AngularVelocity
|
||||
{
|
||||
get => _angVelocity;
|
||||
set
|
||||
@@ -76,6 +76,41 @@ namespace Robust.Server.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Current momentum of the entity in kilogram meters per second
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public override Vector2 Momentum
|
||||
{
|
||||
get => LinearVelocity * Mass;
|
||||
set => LinearVelocity = value / Mass;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The current status of the object
|
||||
/// </summary>
|
||||
public override BodyStatus Status
|
||||
{
|
||||
get => _status;
|
||||
set => _status = value;
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Represents a virtual controller acting on the physics component.
|
||||
/// </summary>
|
||||
public override VirtualController Controller
|
||||
{
|
||||
get => _controller;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Whether this component is on the ground
|
||||
/// </summary>
|
||||
public override bool OnGround => Status == BodyStatus.OnGround &&
|
||||
!IoCManager.Resolve<IPhysicsManager>()
|
||||
.IsWeightless(Owner.Transform.GridPosition);
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool EdgeSlide { get => edgeSlide; set => edgeSlide = value; }
|
||||
private bool edgeSlide = true;
|
||||
@@ -91,6 +126,11 @@ namespace Robust.Server.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
public void SetController<T>() where T: VirtualController, new()
|
||||
{
|
||||
_controller = new T {ControlledComponent = this};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void ExposeData(ObjectSerializer serializer)
|
||||
{
|
||||
@@ -100,72 +140,20 @@ namespace Robust.Server.GameObjects
|
||||
serializer.DataField(ref _linVelocity, "vel", Vector2.Zero);
|
||||
serializer.DataField(ref _angVelocity, "avel", 0.0f);
|
||||
serializer.DataField(ref edgeSlide, "edgeslide", true);
|
||||
serializer.DataField(ref _anchored, "Anchored", true);
|
||||
serializer.DataField(ref _anchored, "Anchored", false);
|
||||
serializer.DataField(ref _status, "Status", BodyStatus.OnGround);
|
||||
serializer.DataField(ref _controller, "Controller", null);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
return new PhysicsComponentState(_mass, _linVelocity);
|
||||
return new PhysicsComponentState(_mass, _linVelocity, _angVelocity);
|
||||
}
|
||||
|
||||
public override void HandleMessage(ComponentMessage message, IComponent component)
|
||||
public void RemoveController()
|
||||
{
|
||||
base.HandleMessage(message, component);
|
||||
|
||||
switch (message)
|
||||
{
|
||||
case BumpedEntMsg msg:
|
||||
if (Anchored)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!msg.Entity.TryGetComponent(out PhysicsComponent physicsComponent))
|
||||
{
|
||||
return;
|
||||
}
|
||||
physicsComponent.AddVelocityConsumer(this);
|
||||
break;
|
||||
}
|
||||
_controller = null;
|
||||
}
|
||||
|
||||
private List<PhysicsComponent> VelocityConsumers { get; } = new List<PhysicsComponent>();
|
||||
|
||||
public List<PhysicsComponent> GetVelocityConsumers()
|
||||
{
|
||||
var result = new List<PhysicsComponent> { this };
|
||||
foreach(var velocityConsumer in VelocityConsumers)
|
||||
{
|
||||
result.AddRange(velocityConsumer.GetVelocityConsumers());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void AddVelocityConsumer(PhysicsComponent physicsComponent)
|
||||
{
|
||||
if (!physicsComponent.VelocityConsumers.Contains(this) && !VelocityConsumers.Contains(physicsComponent))
|
||||
{
|
||||
VelocityConsumers.Add(physicsComponent);
|
||||
}
|
||||
}
|
||||
|
||||
internal void ClearVelocityConsumers()
|
||||
{
|
||||
VelocityConsumers.ForEach(x => x.ClearVelocityConsumers());
|
||||
VelocityConsumers.Clear();
|
||||
}
|
||||
|
||||
public bool PreventCollide(IPhysBody collidedwith)
|
||||
{
|
||||
var velocityConsumers = GetVelocityConsumers();
|
||||
if (velocityConsumers.Count == 1 || !collidedwith.Owner.TryGetComponent<PhysicsComponent>(out var physicsComponent))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return velocityConsumers.Contains(physicsComponent);
|
||||
}
|
||||
|
||||
public bool DidMovementCalculations { get; set; } = false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using JetBrains.Annotations;
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Server.Interfaces.Timing;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.GameObjects.Systems;
|
||||
@@ -9,6 +9,10 @@ using Robust.Shared.Interfaces.Map;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.Interfaces.Physics;
|
||||
using Robust.Shared.Interfaces.Random;
|
||||
|
||||
namespace Robust.Server.GameObjects.EntitySystems
|
||||
{
|
||||
@@ -19,10 +23,14 @@ namespace Robust.Server.GameObjects.EntitySystems
|
||||
[Dependency] private readonly IPauseManager _pauseManager;
|
||||
[Dependency] private readonly ITileDefinitionManager _tileDefinitionManager;
|
||||
[Dependency] private readonly IMapManager _mapManager;
|
||||
[Dependency] private readonly IPhysicsManager _physicsManager;
|
||||
[Dependency] private readonly IRobustRandom _random;
|
||||
#pragma warning restore 649
|
||||
|
||||
private const float Epsilon = 1.0e-6f;
|
||||
|
||||
private List<Manifold> _collisionCache = new List<Manifold>();
|
||||
|
||||
public PhysicsSystem()
|
||||
{
|
||||
EntityQuery = new TypeEntityQuery(typeof(PhysicsComponent));
|
||||
@@ -31,193 +39,247 @@ namespace Robust.Server.GameObjects.EntitySystems
|
||||
/// <inheritdoc />
|
||||
public override void Update(float frameTime)
|
||||
{
|
||||
// TODO: manifolds
|
||||
var entities = EntityManager.GetEntities(EntityQuery);
|
||||
SimulateWorld(frameTime, entities);
|
||||
SimulateWorld(frameTime, RelevantEntities.Where(e => !e.Deleted && !_pauseManager.IsEntityPaused(e)).ToList());
|
||||
}
|
||||
|
||||
private void SimulateWorld(float frameTime, IEnumerable<IEntity> entities)
|
||||
private void SimulateWorld(float frameTime, ICollection<IEntity> entities)
|
||||
{
|
||||
// simulation can introduce deleted entities into the query results
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
if (entity.Deleted)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var physics = entity.GetComponent<PhysicsComponent>();
|
||||
|
||||
if (_pauseManager.IsEntityPaused(entity))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
physics.Controller?.UpdateBeforeProcessing();
|
||||
}
|
||||
|
||||
HandleMovement(_mapManager, _tileDefinitionManager, entity, frameTime);
|
||||
// Calculate collisions and store them in the cache
|
||||
ProcessCollisions();
|
||||
|
||||
// Remove all entities that were deleted during collision handling
|
||||
foreach (var entity in entities.Where(e => e.Deleted).ToList())
|
||||
{
|
||||
entities.Remove(entity);
|
||||
}
|
||||
|
||||
// Process frictional forces
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
ProcessFriction(entity, frameTime);
|
||||
}
|
||||
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
if (entity.Deleted)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
var physics = entity.GetComponent<PhysicsComponent>();
|
||||
|
||||
DoMovement(entity, frameTime);
|
||||
physics.Controller?.UpdateAfterProcessing();
|
||||
}
|
||||
|
||||
// Remove all entities that were deleted due to the controller
|
||||
foreach (var entity in entities.Where(e => e.Deleted).ToList())
|
||||
{
|
||||
entities.Remove(entity);
|
||||
}
|
||||
|
||||
const float solveIterations = 3.0f;
|
||||
|
||||
for (var i = 0; i < solveIterations; i++)
|
||||
{
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
UpdatePosition(entity, frameTime / solveIterations);
|
||||
}
|
||||
FixClipping(_collisionCache);
|
||||
}
|
||||
}
|
||||
|
||||
private static void HandleMovement(IMapManager mapManager, ITileDefinitionManager tileDefinitionManager, IEntity entity, float frameTime)
|
||||
// Runs collision behavior and updates cache
|
||||
private void ProcessCollisions()
|
||||
{
|
||||
if (entity.Deleted)
|
||||
_collisionCache.Clear();
|
||||
var collisionsWith = new Dictionary<ICollideBehavior, int>();
|
||||
var physicsComponents = new Dictionary<ICollidableComponent, PhysicsComponent>();
|
||||
var combinations = new List<(EntityUid, EntityUid)>();
|
||||
var entities = RelevantEntities.ToList();
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
// Ugh let's hope this fixes the crashes.
|
||||
return;
|
||||
}
|
||||
|
||||
var velocity = entity.GetComponent<PhysicsComponent>();
|
||||
if (velocity.DidMovementCalculations)
|
||||
{
|
||||
velocity.DidMovementCalculations = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (velocity.AngularVelocity == 0 && velocity.LinearVelocity == Vector2.Zero)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var transform = entity.Transform;
|
||||
if (ContainerHelpers.IsInContainer(transform.Owner))
|
||||
{
|
||||
transform.Parent.Owner.SendMessage(transform, new RelayMovementEntityMessage(entity));
|
||||
velocity.LinearVelocity = Vector2.Zero;
|
||||
return;
|
||||
}
|
||||
|
||||
var velocityConsumers = velocity.GetVelocityConsumers();
|
||||
var initialMovement = velocity.LinearVelocity;
|
||||
int velocityConsumerCount;
|
||||
float totalMass;
|
||||
Vector2 lowestMovement;
|
||||
var tile =
|
||||
mapManager.GetGrid(entity.Transform.GridID).GetTileRef(entity.Transform.GridPosition).Tile;
|
||||
bool hasGravity = mapManager.GetGrid(entity.Transform.GridID).HasGravity && !tile.IsEmpty;
|
||||
do
|
||||
{
|
||||
velocityConsumerCount = velocityConsumers.Count;
|
||||
totalMass = 0;
|
||||
lowestMovement = initialMovement;
|
||||
var copy = new List<Vector2>(velocityConsumers.Count);
|
||||
float totalFriction = 0;
|
||||
foreach (var consumer in velocityConsumers)
|
||||
if (entity.Deleted) continue;
|
||||
if (entity.TryGetComponent<CollidableComponent>(out var a))
|
||||
{
|
||||
var movement = lowestMovement;
|
||||
totalMass += consumer.Mass;
|
||||
if (hasGravity)
|
||||
foreach (var b in a.GetCollidingEntities(Vector2.Zero).Select(e => e.GetComponent<CollidableComponent>()))
|
||||
{
|
||||
totalFriction += GetFriction(tileDefinitionManager, mapManager, consumer.Owner);
|
||||
movement *= velocity.Mass / (totalMass != 0 ? totalMass + (totalMass * totalFriction) : 1);
|
||||
if (combinations.Contains((a.Owner.Uid, b.Owner.Uid)) ||
|
||||
combinations.Contains((b.Owner.Uid, a.Owner.Uid))) continue;
|
||||
combinations.Add((a.Owner.Uid, b.Owner.Uid));
|
||||
if (a.Owner.TryGetComponent<PhysicsComponent>(out var aPhysics))
|
||||
{
|
||||
physicsComponents[a] = aPhysics;
|
||||
if (b.Owner.TryGetComponent<PhysicsComponent>(out var bPhysics))
|
||||
{
|
||||
physicsComponents[b] = bPhysics;
|
||||
_collisionCache.Add(new Manifold(a, b, aPhysics, bPhysics));
|
||||
}
|
||||
else
|
||||
{
|
||||
_collisionCache.Add(new Manifold(a, b, aPhysics, null));
|
||||
}
|
||||
}
|
||||
else if (b.Owner.TryGetComponent<PhysicsComponent>(out var bPhysics))
|
||||
{
|
||||
_collisionCache.Add(new Manifold(a, b, null, bPhysics));
|
||||
}
|
||||
}
|
||||
consumer.AngularVelocity = velocity.AngularVelocity;
|
||||
consumer.LinearVelocity = movement;
|
||||
copy.Add(CalculateMovement(tileDefinitionManager, mapManager, consumer, frameTime, consumer.Owner) / frameTime);
|
||||
}
|
||||
}
|
||||
|
||||
var counter = 0;
|
||||
|
||||
while(GetNextCollision(_collisionCache, counter, out var collision))
|
||||
{
|
||||
counter++;
|
||||
var impulse = _physicsManager.SolveCollisionImpulse(collision);
|
||||
if (physicsComponents.ContainsKey(collision.A))
|
||||
{
|
||||
physicsComponents[collision.A].Momentum -= impulse;
|
||||
}
|
||||
|
||||
copy.Sort(LengthComparer);
|
||||
lowestMovement = copy[0];
|
||||
velocityConsumers = velocity.GetVelocityConsumers();
|
||||
} while (velocityConsumers.Count != velocityConsumerCount);
|
||||
|
||||
velocity.ClearVelocityConsumers();
|
||||
|
||||
foreach (var consumer in velocityConsumers)
|
||||
{
|
||||
consumer.LinearVelocity = lowestMovement;
|
||||
consumer.DidMovementCalculations = true;
|
||||
}
|
||||
|
||||
velocity.DidMovementCalculations = false;
|
||||
}
|
||||
|
||||
private static void DoMovement(IEntity entity, float frameTime)
|
||||
{
|
||||
// TODO: Terrible hack to fix bullets crashing the server.
|
||||
// Should be handled with deferred physics events instead.
|
||||
if (entity.Deleted)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var velocity = entity.GetComponent<PhysicsComponent>();
|
||||
|
||||
if (velocity.LinearVelocity.LengthSquared < Epsilon && velocity.AngularVelocity < Epsilon)
|
||||
return;
|
||||
|
||||
float angImpulse = 0;
|
||||
if (velocity.AngularVelocity > Epsilon)
|
||||
{
|
||||
angImpulse = velocity.AngularVelocity * frameTime;
|
||||
}
|
||||
|
||||
var transform = entity.Transform;
|
||||
transform.LocalRotation += angImpulse;
|
||||
transform.WorldPosition += velocity.LinearVelocity * frameTime;
|
||||
}
|
||||
|
||||
private static Vector2 CalculateMovement(ITileDefinitionManager tileDefinitionManager, IMapManager mapManager, PhysicsComponent velocity, float frameTime, IEntity entity)
|
||||
{
|
||||
if (velocity.Deleted)
|
||||
{
|
||||
// Help crashes.
|
||||
return default;
|
||||
}
|
||||
|
||||
var movement = velocity.LinearVelocity * frameTime;
|
||||
if (movement.LengthSquared <= Epsilon)
|
||||
{
|
||||
return Vector2.Zero;
|
||||
}
|
||||
|
||||
//TODO This is terrible. This needs to calculate the manifold between the two objects.
|
||||
//Check for collision
|
||||
if (entity.TryGetComponent(out CollidableComponent collider))
|
||||
{
|
||||
var collided = collider.TryCollision(movement, true);
|
||||
|
||||
if (collided)
|
||||
if (physicsComponents.ContainsKey(collision.B))
|
||||
{
|
||||
if (velocity.EdgeSlide)
|
||||
{
|
||||
//Slide along the blockage in the non-blocked direction
|
||||
var xBlocked = collider.TryCollision(new Vector2(movement.X, 0));
|
||||
var yBlocked = collider.TryCollision(new Vector2(0, movement.Y));
|
||||
physicsComponents[collision.B].Momentum += impulse;
|
||||
}
|
||||
|
||||
movement = new Vector2(xBlocked ? 0 : movement.X, yBlocked ? 0 : movement.Y);
|
||||
// Apply onCollide behavior
|
||||
var aBehaviors = (collision.A as CollidableComponent).Owner.GetAllComponents<ICollideBehavior>();
|
||||
foreach (var behavior in aBehaviors)
|
||||
{
|
||||
var entity = (collision.B as CollidableComponent).Owner;
|
||||
if (entity.Deleted) continue;
|
||||
behavior.CollideWith(entity);
|
||||
if (collisionsWith.ContainsKey(behavior))
|
||||
{
|
||||
collisionsWith[behavior] += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Stop movement entirely at first blockage
|
||||
movement = new Vector2(0, 0);
|
||||
collisionsWith[behavior] = 1;
|
||||
}
|
||||
}
|
||||
var bBehaviors = (collision.B as CollidableComponent).Owner.GetAllComponents<ICollideBehavior>();
|
||||
foreach (var behavior in bBehaviors)
|
||||
{
|
||||
var entity = (collision.A as CollidableComponent).Owner;
|
||||
if (entity.Deleted) continue;
|
||||
behavior.CollideWith(entity);
|
||||
if (collisionsWith.ContainsKey(behavior))
|
||||
{
|
||||
collisionsWith[behavior] += 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
collisionsWith[behavior] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return movement;
|
||||
foreach (var behavior in collisionsWith.Keys)
|
||||
{
|
||||
behavior.PostCollide(collisionsWith[behavior]);
|
||||
}
|
||||
}
|
||||
|
||||
private static float GetFriction(ITileDefinitionManager tileDefinitionManager, IMapManager mapManager, IEntity entity)
|
||||
private bool GetNextCollision(List<Manifold> collisions, int counter, out Manifold collision)
|
||||
{
|
||||
if (entity.TryGetComponent(out CollidableComponent collider) && collider.IsScrapingFloor)
|
||||
// The *4 is completely arbitrary
|
||||
if (counter > collisions.Count * 4)
|
||||
{
|
||||
collision = collisions[0];
|
||||
return false;
|
||||
}
|
||||
var indexes = new List<int>();
|
||||
for (int i = 0; i < collisions.Count; i++)
|
||||
{
|
||||
indexes.Add(i);
|
||||
}
|
||||
_random.Shuffle(indexes);
|
||||
foreach (var index in indexes)
|
||||
{
|
||||
if (collisions[index].Unresolved)
|
||||
{
|
||||
collision = collisions[index];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
collision = collisions[0];
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ProcessFriction(IEntity entity, float frameTime)
|
||||
{
|
||||
// A constant that scales frictional force to work with the rest of the engine
|
||||
const float frictionScalingConstant = 60.0f;
|
||||
|
||||
var physics = entity.GetComponent<PhysicsComponent>();
|
||||
|
||||
if (physics.LinearVelocity == Vector2.Zero) return;
|
||||
|
||||
// Calculate frictional force
|
||||
var friction = GetFriction(entity) * frameTime * frictionScalingConstant;
|
||||
|
||||
// Clamp friction because friction can't make you accelerate backwards
|
||||
friction = Math.Min(friction, physics.LinearVelocity.Length);
|
||||
|
||||
// No multiplication/division by mass here since that would be redundant.
|
||||
var frictionVelocityChange = physics.LinearVelocity.Normalized * -friction;
|
||||
|
||||
physics.LinearVelocity += frictionVelocityChange;
|
||||
}
|
||||
|
||||
private void UpdatePosition(IEntity entity, float frameTime)
|
||||
{
|
||||
var physics = entity.GetComponent<PhysicsComponent>();
|
||||
physics.LinearVelocity = new Vector2(Math.Abs(physics.LinearVelocity.X) < Epsilon ? 0.0f : physics.LinearVelocity.X, Math.Abs(physics.LinearVelocity.Y) < Epsilon ? 0.0f : physics.LinearVelocity.Y);
|
||||
if (physics.Anchored || physics.LinearVelocity == Vector2.Zero && Math.Abs(physics.AngularVelocity) < Epsilon) return;
|
||||
|
||||
if (ContainerHelpers.IsInContainer(entity) && physics.LinearVelocity != Vector2.Zero)
|
||||
{
|
||||
entity.Transform.Parent.Owner.SendMessage(entity.Transform, new RelayMovementEntityMessage(entity));
|
||||
// This prevents redundant messages from being sent if solveIterations > 1 and also simulates the entity "colliding" against the locker door when it opens.
|
||||
physics.LinearVelocity = Vector2.Zero;
|
||||
}
|
||||
|
||||
physics.Owner.Transform.WorldRotation += physics.AngularVelocity * frameTime;
|
||||
physics.Owner.Transform.WorldPosition += physics.LinearVelocity * frameTime;
|
||||
}
|
||||
|
||||
// Based off of Randy Gaul's ImpulseEngine code
|
||||
private void FixClipping(List<Manifold> collisions)
|
||||
{
|
||||
const float allowance = 0.05f;
|
||||
const float percent = 0.4f;
|
||||
foreach (var collision in collisions)
|
||||
{
|
||||
var penetration = _physicsManager.CalculatePenetration(collision.A, collision.B);
|
||||
if (penetration > allowance)
|
||||
{
|
||||
var correction = collision.Normal * Math.Abs(penetration) * percent;
|
||||
if (collision.APhysics != null && !(collision.APhysics as PhysicsComponent).Anchored && !collision.APhysics.Deleted)
|
||||
collision.APhysics.Owner.Transform.WorldPosition -= correction;
|
||||
if (collision.BPhysics != null && !(collision.BPhysics as PhysicsComponent).Anchored && !collision.BPhysics.Deleted)
|
||||
collision.BPhysics.Owner.Transform.WorldPosition += correction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private float GetFriction(IEntity entity)
|
||||
{
|
||||
if (entity.HasComponent<CollidableComponent>() && entity.TryGetComponent(out PhysicsComponent physics) && physics.OnGround)
|
||||
{
|
||||
var location = entity.Transform;
|
||||
var grid = mapManager.GetGrid(location.GridPosition.GridID);
|
||||
var grid = _mapManager.GetGrid(location.GridPosition.GridID);
|
||||
var tile = grid.GetTileRef(location.GridPosition);
|
||||
var tileDef = tileDefinitionManager[tile.Tile.TypeId];
|
||||
var tileDef = _tileDefinitionManager[tile.Tile.TypeId];
|
||||
return tileDef.Friction;
|
||||
}
|
||||
return 0;
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
private static readonly IComparer<Vector2> LengthComparer =
|
||||
Comparer<Vector2>.Create((a, b) => a.LengthSquared.CompareTo(b.LengthSquared));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,16 +7,6 @@ using Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.Shared.GameObjects
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
public class BumpedEntMsg : ComponentMessage
|
||||
{
|
||||
public IEntity Entity { get; }
|
||||
|
||||
public BumpedEntMsg(IEntity entity)
|
||||
{
|
||||
Entity = entity;
|
||||
}
|
||||
}
|
||||
|
||||
public class RelayMovementEntityMessage : ComponentMessage
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Physics;
|
||||
@@ -17,9 +18,8 @@ namespace Robust.Shared.GameObjects.Components
|
||||
[Dependency] private readonly IPhysicsManager _physicsManager;
|
||||
#pragma warning restore 649
|
||||
|
||||
private bool _collisionEnabled;
|
||||
private bool _isHardCollidable;
|
||||
private bool _isScrapingFloor;
|
||||
private bool _canCollide;
|
||||
private BodyStatus _status;
|
||||
private BodyType _bodyType;
|
||||
private List<IPhysShape> _physShapes = new List<IPhysShape>();
|
||||
|
||||
@@ -40,9 +40,8 @@ namespace Robust.Shared.GameObjects.Components
|
||||
{
|
||||
base.ExposeData(serializer);
|
||||
|
||||
serializer.DataField(ref _collisionEnabled, "on", true);
|
||||
serializer.DataField(ref _isHardCollidable, "hard", true);
|
||||
serializer.DataField(ref _isScrapingFloor, "IsScrapingFloor", false);
|
||||
serializer.DataField(ref _canCollide, "on", true);
|
||||
serializer.DataField(ref _status, "Status", BodyStatus.OnGround);
|
||||
serializer.DataField(ref _bodyType, "bodyType", BodyType.None);
|
||||
serializer.DataField(ref _physShapes, "shapes", new List<IPhysShape>{new PhysShapeAabb()});
|
||||
}
|
||||
@@ -50,7 +49,7 @@ namespace Robust.Shared.GameObjects.Components
|
||||
/// <inheritdoc />
|
||||
public override ComponentState GetComponentState()
|
||||
{
|
||||
return new CollidableComponentState(_collisionEnabled, _isHardCollidable, _isScrapingFloor, _physShapes);
|
||||
return new CollidableComponentState(_canCollide, _status, _physShapes);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -61,9 +60,8 @@ namespace Robust.Shared.GameObjects.Components
|
||||
|
||||
var newState = (CollidableComponentState)curState;
|
||||
|
||||
_collisionEnabled = newState.CollisionEnabled;
|
||||
_isHardCollidable = newState.HardCollidable;
|
||||
_isScrapingFloor = newState.ScrapingFloor;
|
||||
_canCollide = newState.CanCollide;
|
||||
_status = newState.Status;
|
||||
|
||||
//TODO: Is this always true?
|
||||
if (newState.PhysShapes != null)
|
||||
@@ -118,22 +116,10 @@ namespace Robust.Shared.GameObjects.Components
|
||||
/// Enables or disabled collision processing of this component.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool CollisionEnabled
|
||||
public bool CanCollide
|
||||
{
|
||||
get => _collisionEnabled;
|
||||
set
|
||||
{
|
||||
_collisionEnabled = value;
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new CollisionEnabledEvent(Owner.Uid, value));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool IsHardCollidable
|
||||
{
|
||||
get => _isHardCollidable;
|
||||
set => _isHardCollidable = value;
|
||||
get => _canCollide;
|
||||
set => _canCollide = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -167,28 +153,10 @@ namespace Robust.Shared.GameObjects.Components
|
||||
}
|
||||
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public bool IsScrapingFloor
|
||||
public BodyStatus Status
|
||||
{
|
||||
get => _isScrapingFloor;
|
||||
set => _isScrapingFloor = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
void IPhysBody.Bumped(IEntity bumpedby)
|
||||
{
|
||||
SendMessage(new BumpedEntMsg(bumpedby));
|
||||
UpdateEntityTree();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
void IPhysBody.Bump(List<IEntity> bumpedinto)
|
||||
{
|
||||
var collidecomponents = Owner.GetAllComponents<ICollideBehavior>().ToList();
|
||||
|
||||
for (var i = 0; i < collidecomponents.Count; i++)
|
||||
{
|
||||
collidecomponents[i].CollideWith(bumpedinto);
|
||||
}
|
||||
get => _status;
|
||||
set => _status = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -205,7 +173,6 @@ namespace Robust.Shared.GameObjects.Components
|
||||
protected override void Startup()
|
||||
{
|
||||
base.Startup();
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new CollisionEnabledEvent(Owner.Uid, _collisionEnabled));
|
||||
_physicsManager.AddBody(this);
|
||||
}
|
||||
|
||||
@@ -213,17 +180,17 @@ namespace Robust.Shared.GameObjects.Components
|
||||
protected override void Shutdown()
|
||||
{
|
||||
_physicsManager.RemoveBody(this);
|
||||
Owner.EntityManager.EventBus.RaiseEvent(EventSource.Local, new CollisionEnabledEvent(Owner.Uid, false));
|
||||
base.Shutdown();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public bool TryCollision(Vector2 offset, bool bump = false)
|
||||
public bool IsColliding(Vector2 offset)
|
||||
{
|
||||
if (!_collisionEnabled || CollisionMask == 0x0)
|
||||
return false;
|
||||
return _physicsManager.IsColliding(this, offset);
|
||||
}
|
||||
|
||||
return _physicsManager.TryCollide(Owner, offset, bump);
|
||||
public IEnumerable<IEntity> GetCollidingEntities(Vector2 offset)
|
||||
{
|
||||
return _physicsManager.GetCollidingEntities(this, offset);
|
||||
}
|
||||
|
||||
public bool UpdatePhysicsTree()
|
||||
@@ -240,18 +207,15 @@ namespace Robust.Shared.GameObjects.Components
|
||||
}
|
||||
|
||||
private bool UpdateEntityTree() => Owner.EntityManager.UpdateEntityTree(Owner);
|
||||
}
|
||||
|
||||
public class CollisionEnabledEvent : EntitySystemMessage
|
||||
{
|
||||
public bool Value { get; }
|
||||
public EntityUid Owner { get; }
|
||||
|
||||
|
||||
public CollisionEnabledEvent(EntityUid uid, bool value)
|
||||
public bool IsOnGround()
|
||||
{
|
||||
Owner = uid;
|
||||
Value = value;
|
||||
return Status == BodyStatus.OnGround;
|
||||
}
|
||||
|
||||
public bool IsInAir()
|
||||
{
|
||||
return Status == BodyStatus.InAir;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,17 +8,15 @@ namespace Robust.Shared.GameObjects.Components
|
||||
[Serializable, NetSerializable]
|
||||
public class CollidableComponentState : ComponentState
|
||||
{
|
||||
public readonly bool CollisionEnabled;
|
||||
public readonly bool HardCollidable;
|
||||
public readonly bool ScrapingFloor;
|
||||
public readonly bool CanCollide;
|
||||
public readonly BodyStatus Status;
|
||||
public readonly List<IPhysShape> PhysShapes;
|
||||
|
||||
public CollidableComponentState(bool collisionEnabled, bool hardCollidable, bool scrapingFloor, List<IPhysShape> physShapes)
|
||||
public CollidableComponentState(bool canCollide, BodyStatus status, List<IPhysShape> physShapes)
|
||||
: base(NetIDs.COLLIDABLE)
|
||||
{
|
||||
CollisionEnabled = collisionEnabled;
|
||||
HardCollidable = hardCollidable;
|
||||
ScrapingFloor = scrapingFloor;
|
||||
CanCollide = canCollide;
|
||||
Status = status;
|
||||
PhysShapes = physShapes;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,10 @@ namespace Robust.Shared.GameObjects.Components
|
||||
{
|
||||
public interface ICollidableComponent : IComponent, IPhysBody
|
||||
{
|
||||
bool TryCollision(Vector2 offset, bool bump = false);
|
||||
|
||||
bool IsColliding(Vector2 offset);
|
||||
|
||||
IEnumerable<IEntity> GetCollidingEntities(Vector2 offset);
|
||||
bool UpdatePhysicsTree();
|
||||
|
||||
void RemovedFromPhysicsTree(MapId mapId);
|
||||
@@ -23,6 +25,12 @@ namespace Robust.Shared.GameObjects.Components
|
||||
|
||||
public interface ICollideBehavior
|
||||
{
|
||||
void CollideWith(List<IEntity> collidedwith);
|
||||
void CollideWith(IEntity collidedWith);
|
||||
|
||||
/// <summary>
|
||||
/// Called after all collisions have been processed, as well as how many collisions occured
|
||||
/// </summary>
|
||||
/// <param name="collisionCount"></param>
|
||||
void PostCollide(int collisionCount) { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,20 +16,26 @@ namespace Robust.Shared.GameObjects
|
||||
public readonly int Mass;
|
||||
|
||||
/// <summary>
|
||||
/// Current velocity of the entity.
|
||||
/// Current linear velocity of the entity.
|
||||
/// </summary>
|
||||
public readonly Vector2 Velocity;
|
||||
public readonly Vector2 LinearVelocity;
|
||||
|
||||
/// <summary>
|
||||
/// Current angular velocity of the entity.
|
||||
/// </summary>
|
||||
public readonly float AngularVelocity;
|
||||
|
||||
/// <summary>
|
||||
/// Constructs a new state snapshot of a PhysicsComponent.
|
||||
/// </summary>
|
||||
/// <param name="mass">Current Mass of the entity.</param>
|
||||
/// <param name="velocity">Current Velocity of the entity.</param>
|
||||
public PhysicsComponentState(float mass, Vector2 velocity)
|
||||
public PhysicsComponentState(float mass, Vector2 linearVelocity, float angularVelocity)
|
||||
: base(NetIDs.PHYSICS)
|
||||
{
|
||||
Mass = (int) Math.Round(mass *1000); // rounds kg to nearest gram
|
||||
Velocity = velocity;
|
||||
LinearVelocity = linearVelocity;
|
||||
AngularVelocity = angularVelocity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,8 +262,8 @@ namespace Robust.Shared.GameObjects.Components.Transform
|
||||
var newPos = Parent.InvWorldMatrix.Transform(value);
|
||||
|
||||
// float rounding error guard, if the offset is less than 1mm ignore it
|
||||
if ((newPos - GetLocalPosition()).LengthSquared < 1.0E-3)
|
||||
return;
|
||||
//if ((newPos - GetLocalPosition()).LengthSquared < 1.0E-3)
|
||||
// return;
|
||||
|
||||
if (_localPosition == newPos)
|
||||
return;
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Map;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics;
|
||||
@@ -20,39 +22,77 @@ namespace Robust.Shared.Interfaces.Physics
|
||||
/// <param name="collider">Collision rectangle to check</param>
|
||||
/// <param name="map">Map to check on</param>
|
||||
/// <returns>true if collides, false if not</returns>
|
||||
bool IsColliding(Box2 collider, MapId map);
|
||||
bool TryCollideRect(Box2 collider, MapId map);
|
||||
|
||||
bool TryCollide(IEntity entity, Vector2 offset, bool bump = true);
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a certain grid position is weightless or not
|
||||
/// </summary>
|
||||
/// <param name="gridPosition"></param>
|
||||
/// <returns></returns>
|
||||
bool IsWeightless(GridCoordinates gridPosition);
|
||||
|
||||
/// <summary>
|
||||
/// Get all entities colliding with a certain body.
|
||||
/// </summary>
|
||||
/// <param name="body"></param>
|
||||
/// <returns></returns>
|
||||
IEnumerable<IEntity> GetCollidingEntities(IPhysBody body, Vector2 offset, bool approximate = true);
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether a body is colliding
|
||||
/// </summary>
|
||||
/// <param name="body"></param>
|
||||
/// <param name="offset"></param>
|
||||
/// <returns></returns>
|
||||
bool IsColliding(IPhysBody body, Vector2 offset);
|
||||
|
||||
void AddBody(IPhysBody physBody);
|
||||
void RemoveBody(IPhysBody physBody);
|
||||
|
||||
/// <summary>
|
||||
/// Casts a ray in the world and returns the first thing it hit.
|
||||
/// Casts a ray in the world and returns the first entity it hits, or a list of all entities it hits.
|
||||
/// </summary>
|
||||
/// <param name="mapId"></param>
|
||||
/// <param name="ray">Ray to cast in the world.</param>
|
||||
/// <param name="maxLength">Maximum length of the ray in meters.</param>
|
||||
/// <param name="ignoredEnt">A single entity that can be ignored by the RayCast. Useful if the ray starts inside the body of an entity.</param>
|
||||
/// <param name="ignoreNonHardCollidables">If true, the RayCast will ignore any bodies that aren't hard collidables.</param>
|
||||
/// <returns>A result object describing the hit, if any.</returns>
|
||||
RayCastResults IntersectRay(MapId mapId, CollisionRay ray, float maxLength = 50, IEntity ignoredEnt = null, bool ignoreNonHardCollidables = false);
|
||||
/// <param name="returnOnFirstHit">If false, will return a list of everything it hits, otherwise will just return a list of the first entity hit</param>
|
||||
/// <returns>An enumerable of either the first entity hit or everything hit</returns>
|
||||
IEnumerable<RayCastResults> IntersectRay(MapId mapId, CollisionRay ray, float maxLength = 50, IEntity ignoredEnt = null, bool returnOnFirstHit = true);
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Casts a ray in the world and returns the first thing it hit.
|
||||
/// Calculates the normal vector for two colliding bodies
|
||||
/// </summary>
|
||||
/// <param name="target"></param>
|
||||
/// <param name="source"></param>
|
||||
/// <returns></returns>
|
||||
Vector2 CalculateNormal(ICollidableComponent target, ICollidableComponent source);
|
||||
|
||||
/// <summary>
|
||||
/// Calculates the penetration depth of the axis-of-least-penetration for a
|
||||
/// </summary>
|
||||
/// <param name="target"></param>
|
||||
/// <param name="source"></param>
|
||||
/// <returns></returns>
|
||||
float CalculatePenetration(ICollidableComponent target, ICollidableComponent source);
|
||||
|
||||
Vector2 SolveCollisionImpulse(Manifold manifold);
|
||||
|
||||
/// <summary>
|
||||
/// Casts a ray in the world, returning the first entity it hits (or all entities it hits, if so specified)
|
||||
/// </summary>
|
||||
/// <param name="mapId"></param>
|
||||
/// <param name="ray">Ray to cast in the world.</param>
|
||||
/// <param name="maxLength">Maximum length of the ray in meters.</param>
|
||||
/// <param name="predicate">A predicate to check whether to ignore an entity or not. If it returns true, it will be ignored.</param>
|
||||
/// <param name="ignoreNonHardCollidables">If true, the RayCast will ignore any bodies that aren't hard collidables.</param>
|
||||
/// <param name="returnOnFirstHit">If true, will only include the first hit entity in results. Otherwise, returns all of them.</param>
|
||||
/// <returns>A result object describing the hit, if any.</returns>
|
||||
RayCastResults IntersectRayWithPredicate(MapId mapId, CollisionRay ray, float maxLength = 50, Func<IEntity, bool> predicate = null, bool ignoreNonHardCollidables = false);
|
||||
IEnumerable<RayCastResults> IntersectRayWithPredicate(MapId mapId, CollisionRay ray, float maxLength = 50, Func<IEntity, bool> predicate = null, bool returnOnFirstHit = true);
|
||||
|
||||
event Action<DebugRayData> DebugDrawRay;
|
||||
|
||||
IEnumerable<(IPhysBody, IPhysBody)> GetCollisions();
|
||||
|
||||
bool Update(IPhysBody collider);
|
||||
|
||||
void RemovedFromMap(IPhysBody body, MapId mapId);
|
||||
@@ -61,15 +101,70 @@ namespace Robust.Shared.Interfaces.Physics
|
||||
|
||||
public struct DebugRayData
|
||||
{
|
||||
public DebugRayData(Ray ray, float maxLength, RayCastResults results)
|
||||
public DebugRayData(Ray ray, float maxLength, [CanBeNull] RayCastResults? results)
|
||||
{
|
||||
Ray = ray;
|
||||
MaxLength = maxLength;
|
||||
Results = results;
|
||||
}
|
||||
|
||||
public Ray Ray { get; }
|
||||
public RayCastResults Results { get; }
|
||||
public Ray Ray
|
||||
{
|
||||
get;
|
||||
}
|
||||
|
||||
[CanBeNull]
|
||||
public RayCastResults? Results { get; }
|
||||
public float MaxLength { get; }
|
||||
}
|
||||
|
||||
public struct Manifold
|
||||
{
|
||||
public Vector2 RelativeVelocity
|
||||
{
|
||||
get
|
||||
{
|
||||
if (APhysics != null)
|
||||
{
|
||||
if (BPhysics != null)
|
||||
{
|
||||
return BPhysics.LinearVelocity - APhysics.LinearVelocity;
|
||||
}
|
||||
else
|
||||
{
|
||||
return -APhysics.LinearVelocity;
|
||||
}
|
||||
}
|
||||
|
||||
if (BPhysics != null)
|
||||
{
|
||||
return BPhysics.LinearVelocity;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Vector2.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
public readonly Vector2 Normal;
|
||||
public readonly ICollidableComponent A;
|
||||
public readonly ICollidableComponent B;
|
||||
[CanBeNull] public SharedPhysicsComponent APhysics;
|
||||
[CanBeNull] public SharedPhysicsComponent BPhysics;
|
||||
|
||||
public float InvAMass => 1 / APhysics?.Mass ?? 0.0f;
|
||||
public float InvBMass => 1 / BPhysics?.Mass ?? 0.0f;
|
||||
|
||||
public bool Unresolved => Vector2.Dot(RelativeVelocity, Normal) < 0;
|
||||
|
||||
public Manifold(ICollidableComponent A, ICollidableComponent B, [CanBeNull] SharedPhysicsComponent aPhysics, [CanBeNull] SharedPhysicsComponent bPhysics)
|
||||
{
|
||||
var physicsManager = IoCManager.Resolve<IPhysicsManager>();
|
||||
this.A = A;
|
||||
this.B = B;
|
||||
Normal = physicsManager.CalculateNormal(A, B);
|
||||
APhysics = aPhysics;
|
||||
BPhysics = bPhysics;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -471,8 +471,7 @@ namespace Robust.Shared.Map
|
||||
gridComp.GridIndex = grid.Index;
|
||||
|
||||
var collideComp = newEnt.AddComponent<CollidableComponent>();
|
||||
collideComp.CollisionEnabled = true;
|
||||
collideComp.IsHardCollidable = true;
|
||||
collideComp.CanCollide = true;
|
||||
collideComp.PhysicsShapes.Add(new PhysShapeGrid(grid));
|
||||
|
||||
newEnt.Transform.AttachParent(_entityManager.GetEntity(_mapEntities[currentMapID]));
|
||||
|
||||
@@ -6,7 +6,7 @@ using Robust.Shared.Maths;
|
||||
namespace Robust.Shared.Physics
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
///
|
||||
/// </summary>
|
||||
public interface IPhysBody
|
||||
{
|
||||
@@ -28,14 +28,9 @@ namespace Robust.Shared.Physics
|
||||
List<IPhysShape> PhysicsShapes { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Enables or disabled collision processing of this body.
|
||||
/// Whether or not this body can collide.
|
||||
/// </summary>
|
||||
bool CollisionEnabled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// True if collisions should prevent movement, or just trigger bumps.
|
||||
/// </summary>
|
||||
bool IsHardCollidable { get; set; }
|
||||
bool CanCollide { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Bitmask of the collision layers this body is a part of. The layers are calculated from
|
||||
@@ -49,18 +44,6 @@ namespace Robust.Shared.Physics
|
||||
/// </summary>
|
||||
int CollisionMask { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Called when the physBody is bumped into by someone/something
|
||||
/// </summary>
|
||||
/// <param name="bumpedby"></param>
|
||||
void Bumped(IEntity bumpedby);
|
||||
|
||||
/// <summary>
|
||||
/// Called when the physBody bumps into this entity
|
||||
/// </summary>
|
||||
/// <param name="bumpedinto"></param>
|
||||
void Bump(List<IEntity> bumpedinto);
|
||||
|
||||
/// <summary>
|
||||
/// The map index this physBody is located upon
|
||||
/// </summary>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects.Components;
|
||||
using Robust.Shared.Interfaces.GameObjects;
|
||||
using Robust.Shared.Interfaces.Map;
|
||||
@@ -18,7 +17,9 @@ namespace Robust.Shared.Physics
|
||||
/// <inheritdoc />
|
||||
public class PhysicsManager : IPhysicsManager
|
||||
{
|
||||
private readonly List<IPhysBody> _results = new List<IPhysBody>();
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly IMapManager _mapManager;
|
||||
#pragma warning restore 649
|
||||
|
||||
private readonly ConcurrentDictionary<MapId,BroadPhase> _treesPerMap =
|
||||
new ConcurrentDictionary<MapId, BroadPhase>();
|
||||
@@ -26,20 +27,19 @@ namespace Robust.Shared.Physics
|
||||
private BroadPhase this[MapId mapId] => _treesPerMap.GetOrAdd(mapId, _ => new BroadPhase());
|
||||
|
||||
/// <summary>
|
||||
/// returns true if collider intersects a physBody under management. Does not trigger Bump.
|
||||
/// returns true if collider intersects a physBody under management.
|
||||
/// </summary>
|
||||
/// <param name="collider">Rectangle to check for collision</param>
|
||||
/// <param name="map">Map ID to filter</param>
|
||||
/// <returns></returns>
|
||||
public bool IsColliding(Box2 collider, MapId map)
|
||||
public bool TryCollideRect(Box2 collider, MapId map)
|
||||
{
|
||||
foreach (var body in this[map].Query(collider))
|
||||
{
|
||||
if (!body.CollisionEnabled || body.CollisionLayer == 0x0)
|
||||
if (!body.CanCollide || body.CollisionLayer == 0x0)
|
||||
continue;
|
||||
|
||||
if (body.MapID == map &&
|
||||
body.IsHardCollidable &&
|
||||
body.WorldAABB.Intersects(collider))
|
||||
return true;
|
||||
}
|
||||
@@ -47,118 +47,97 @@ namespace Robust.Shared.Physics
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// returns true if collider intersects a physBody under management and calls Bump.
|
||||
/// </summary>
|
||||
/// <param name="entity">Rectangle to check for collision</param>
|
||||
/// <param name="offset"></param>
|
||||
/// <param name="bump"></param>
|
||||
/// <returns></returns>
|
||||
public bool TryCollide(IEntity entity, Vector2 offset, bool bump = true)
|
||||
public bool IsWeightless(GridCoordinates gridPosition)
|
||||
{
|
||||
if (entity == null)
|
||||
throw new ArgumentNullException(nameof(entity));
|
||||
|
||||
var collidable = (IPhysBody) entity.GetComponent<ICollidableComponent>();
|
||||
|
||||
// This will never collide with anything
|
||||
if (!collidable.CollisionEnabled || collidable.CollisionLayer == 0x0)
|
||||
return false;
|
||||
|
||||
var colliderAABB = collidable.WorldAABB;
|
||||
if (offset.LengthSquared > 0)
|
||||
{
|
||||
colliderAABB = colliderAABB.Translated(offset);
|
||||
}
|
||||
|
||||
// Test this physBody against every other one.
|
||||
_results.Clear();
|
||||
DoCollisionTest(collidable, colliderAABB, _results);
|
||||
|
||||
// collided with nothing
|
||||
if (_results.Count == 0)
|
||||
return false;
|
||||
|
||||
//See if our collision will be overridden by a component
|
||||
var collisionmodifiers = entity.GetAllComponents<ICollideSpecial>().ToList();
|
||||
var collidedwith = new List<IEntity>();
|
||||
|
||||
//try all of the AABBs against the target rect.
|
||||
var collided = false;
|
||||
foreach (var otherCollidable in _results)
|
||||
{
|
||||
if (!otherCollidable.IsHardCollidable)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var othercollisionmodifiers = otherCollidable.Owner.GetAllComponents<ICollideSpecial>();
|
||||
|
||||
//Provides component level overrides for collision behavior based on the entity we are trying to collide with
|
||||
var preventcollision = false;
|
||||
foreach (var mods in collisionmodifiers)
|
||||
{
|
||||
preventcollision |= mods.PreventCollide(otherCollidable);
|
||||
}
|
||||
foreach (var othermods in othercollisionmodifiers)
|
||||
{
|
||||
preventcollision |= othermods.PreventCollide(collidable);
|
||||
}
|
||||
if (preventcollision)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
collided = true;
|
||||
|
||||
if (!bump)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
otherCollidable.Bumped(entity);
|
||||
collidedwith.Add(otherCollidable.Owner);
|
||||
}
|
||||
|
||||
collidable.Bump(collidedwith);
|
||||
|
||||
//TODO: This needs multi-grid support.
|
||||
return collided;
|
||||
var tile = _mapManager.GetGrid(gridPosition.GridID).GetTileRef(gridPosition).Tile;
|
||||
return !_mapManager.GetGrid(gridPosition.GridID).HasGravity || tile.IsEmpty;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests a physBody against every other registered physBody.
|
||||
/// </summary>
|
||||
/// <param name="physBody">Body being tested.</param>
|
||||
/// <param name="colliderAABB">The AABB of the physBody being tested. This can be IPhysBody.WorldAABB, or a modified version of it.</param>
|
||||
/// <param name="results">An empty list that the function stores all colliding bodies inside of.</param>
|
||||
internal bool DoCollisionTest(IPhysBody physBody, Box2 colliderAABB, List<IPhysBody> results)
|
||||
public Vector2 CalculateNormal(ICollidableComponent target, ICollidableComponent source)
|
||||
{
|
||||
// TODO: Terrible hack to fix bullets crashing the server.
|
||||
// Should be handled with deferred physics events instead.
|
||||
if(physBody.Owner.Deleted)
|
||||
return false;
|
||||
|
||||
var any = false;
|
||||
|
||||
foreach ( var body in this[physBody.MapID].Query(colliderAABB))
|
||||
var manifold = target.WorldAABB.Intersect(source.WorldAABB);
|
||||
if (manifold.IsEmpty()) return Vector2.Zero;
|
||||
if (manifold.Height > manifold.Width)
|
||||
{
|
||||
// X is the axis of seperation
|
||||
var leftDist = source.WorldAABB.Right - target.WorldAABB.Left;
|
||||
var rightDist = target.WorldAABB.Right - source.WorldAABB.Left;
|
||||
return new Vector2(leftDist > rightDist ? 1 : -1, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Y is the axis of seperation
|
||||
var bottomDist = source.WorldAABB.Top - target.WorldAABB.Bottom;
|
||||
var topDist = target.WorldAABB.Top - source.WorldAABB.Bottom;
|
||||
return new Vector2(0, bottomDist > topDist ? 1 : -1);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Terrible hack to fix bullets crashing the server.
|
||||
// Should be handled with deferred physics events instead.
|
||||
public float CalculatePenetration(ICollidableComponent target, ICollidableComponent source)
|
||||
{
|
||||
var manifold = target.WorldAABB.Intersect(source.WorldAABB);
|
||||
if (manifold.IsEmpty()) return 0.0f;
|
||||
return manifold.Height > manifold.Width ? manifold.Width : manifold.Height;
|
||||
}
|
||||
|
||||
// Impulse resolution algorithm based on Box2D's approach in combination with Randy Gaul's Impulse Engine resolution algorithm.
|
||||
public Vector2 SolveCollisionImpulse(Manifold manifold)
|
||||
{
|
||||
var aP = manifold.APhysics;
|
||||
var bP = manifold.BPhysics;
|
||||
if (aP == null && bP == null) return Vector2.Zero;
|
||||
var restitution = 0.01f;
|
||||
var normal = CalculateNormal(manifold.A, manifold.B);
|
||||
var rV = aP != null
|
||||
? bP != null ? bP.LinearVelocity - aP.LinearVelocity : -aP.LinearVelocity
|
||||
: bP.LinearVelocity;
|
||||
|
||||
var vAlongNormal = Vector2.Dot(rV, normal);
|
||||
if (vAlongNormal > 0)
|
||||
{
|
||||
return Vector2.Zero;
|
||||
}
|
||||
|
||||
var impulse = -(1.0f + restitution) * vAlongNormal;
|
||||
// So why the 100.0f instead of 0.0f? Well, because the other object needs to have SOME mass value,
|
||||
// or otherwise the physics object can actually sink in slightly to the physics-less object.
|
||||
// (the 100.0f is equivalent to a mass of 0.01kg)
|
||||
impulse /= (aP != null && aP.Mass > 0.0f ? 1 / aP.Mass : 100.0f) +
|
||||
(bP != null && bP.Mass > 0.0f ? 1 / bP.Mass : 100.0f);
|
||||
return manifold.Normal * impulse;
|
||||
}
|
||||
|
||||
public IEnumerable<IEntity> GetCollidingEntities(IPhysBody physBody, Vector2 offset, bool approximate = true)
|
||||
{
|
||||
var modifiers = physBody.Owner.GetAllComponents<ICollideSpecial>();
|
||||
foreach ( var body in this[physBody.MapID].Query(physBody.WorldAABB, approximate))
|
||||
{
|
||||
if (body.Owner.Deleted) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (CollidesOnMask(physBody, body))
|
||||
{
|
||||
results.Add(body);
|
||||
var preventCollision = false;
|
||||
var otherModifiers = body.Owner.GetAllComponents<ICollideSpecial>();
|
||||
foreach (var modifier in modifiers)
|
||||
{
|
||||
preventCollision |= modifier.PreventCollide(body);
|
||||
}
|
||||
foreach (var modifier in otherModifiers)
|
||||
{
|
||||
preventCollision |= modifier.PreventCollide(physBody);
|
||||
}
|
||||
|
||||
if (preventCollision) continue;
|
||||
yield return body.Owner;
|
||||
}
|
||||
|
||||
any = true;
|
||||
}
|
||||
}
|
||||
|
||||
return any;
|
||||
public bool IsColliding(IPhysBody body, Vector2 offset)
|
||||
{
|
||||
return GetCollidingEntities(body, offset).Any();
|
||||
}
|
||||
|
||||
public static bool CollidesOnMask(IPhysBody a, IPhysBody b)
|
||||
@@ -166,7 +145,7 @@ namespace Robust.Shared.Physics
|
||||
if (a == b)
|
||||
return false;
|
||||
|
||||
if (!a.CollisionEnabled || !b.CollisionEnabled)
|
||||
if (!a.CanCollide || !b.CanCollide)
|
||||
return false;
|
||||
|
||||
if ((a.CollisionMask & b.CollisionLayer) == 0x0 &&
|
||||
@@ -188,10 +167,6 @@ namespace Robust.Shared.Physics
|
||||
}
|
||||
}
|
||||
|
||||
#pragma warning disable 649
|
||||
[Dependency] private readonly IMapManager _mapManager;
|
||||
#pragma warning restore 649
|
||||
|
||||
/// <summary>
|
||||
/// Removes a physBody from the manager
|
||||
/// </summary>
|
||||
@@ -252,23 +227,28 @@ namespace Robust.Shared.Physics
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public RayCastResults IntersectRayWithPredicate(MapId mapId, CollisionRay ray, float maxLength = 50, Func<IEntity, bool> predicate = null, bool ignoreNonHardCollidables = false)
|
||||
public IEnumerable<RayCastResults> IntersectRayWithPredicate(MapId mapId, CollisionRay ray,
|
||||
float maxLength = 50F,
|
||||
Func<IEntity, bool> predicate = null, bool returnOnFirstHit = true)
|
||||
{
|
||||
RayCastResults result = default;
|
||||
List<RayCastResults> results = new List<RayCastResults>();
|
||||
|
||||
this[mapId].Query((ref IPhysBody body, in Vector2 point, float distFromOrigin) => {
|
||||
this[mapId].Query((ref IPhysBody body, in Vector2 point, float distFromOrigin) =>
|
||||
{
|
||||
|
||||
if (returnOnFirstHit && results.Count > 0) return true;
|
||||
|
||||
if (distFromOrigin > maxLength)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (predicate.Invoke(body.Owner))
|
||||
if (predicate != null && predicate.Invoke(body.Owner))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!body.CollisionEnabled)
|
||||
if (!body.CanCollide)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -278,62 +258,26 @@ namespace Robust.Shared.Physics
|
||||
return true;
|
||||
}
|
||||
|
||||
if (ignoreNonHardCollidables && !body.IsHardCollidable)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// ReSharper disable once CompareOfFloatsByEqualityOperator
|
||||
if (result.Distance != 0f && distFromOrigin > result.Distance)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
result = new RayCastResults(distFromOrigin, point, body.Owner);
|
||||
|
||||
var result = new RayCastResults(distFromOrigin, point, body.Owner);
|
||||
results.Add(result);
|
||||
DebugDrawRay?.Invoke(new DebugRayData(ray, maxLength, result));
|
||||
return true;
|
||||
}, ray.Position, ray.Direction);
|
||||
if (results.Count == 0)
|
||||
{
|
||||
DebugDrawRay?.Invoke(new DebugRayData(ray, maxLength, null));
|
||||
}
|
||||
|
||||
DebugDrawRay?.Invoke(new DebugRayData(ray, maxLength, result));
|
||||
|
||||
return result;
|
||||
results.Sort((a, b) => a.Distance.CompareTo(b.Distance));
|
||||
return results;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public RayCastResults IntersectRay(MapId mapId, CollisionRay ray, float maxLength = 50, IEntity ignoredEnt = null, bool ignoreNonHardCollidables = false)
|
||||
=> IntersectRayWithPredicate(mapId, ray, maxLength, entity => entity == ignoredEnt, ignoreNonHardCollidables);
|
||||
public IEnumerable<RayCastResults> IntersectRay(MapId mapId, CollisionRay ray, float maxLength = 50, IEntity ignoredEnt = null, bool returnOnFirstHit = true)
|
||||
=> IntersectRayWithPredicate(mapId, ray, maxLength, entity => entity == ignoredEnt, returnOnFirstHit);
|
||||
|
||||
public event Action<DebugRayData> DebugDrawRay;
|
||||
|
||||
public IEnumerable<(IPhysBody, IPhysBody)> GetCollisions()
|
||||
{
|
||||
foreach (var mapId in _mapManager.GetAllMapIds())
|
||||
{
|
||||
foreach (var collision in this[mapId].GetCollisions(true))
|
||||
{
|
||||
var (a, b) = collision;
|
||||
|
||||
if (!a.CollisionEnabled || !b.CollisionEnabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (((a.CollisionLayer & b.CollisionMask) == 0x0)
|
||||
||(b.CollisionLayer & a.CollisionMask) == 0x0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!a.WorldAABB.Intersects(b.WorldAABB))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return collision;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool Update(IPhysBody collider)
|
||||
=> this[collider.MapID].Update(collider);
|
||||
|
||||
|
||||
@@ -5,10 +5,6 @@ namespace Robust.Shared.Physics
|
||||
{
|
||||
public readonly struct RayCastResults
|
||||
{
|
||||
/// <summary>
|
||||
/// True if an object was indeed hit. False otherwise.
|
||||
/// </summary>
|
||||
public bool DidHitObject => HitEntity != null;
|
||||
|
||||
/// <summary>
|
||||
/// The entity that was hit. <see langword="null" /> if no entity was hit.
|
||||
|
||||
31
Robust.Shared/Physics/SharedPhysicsComponent.cs
Normal file
31
Robust.Shared/Physics/SharedPhysicsComponent.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.Shared.Physics
|
||||
{
|
||||
public abstract class SharedPhysicsComponent: Component
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public override string Name => "Physics";
|
||||
|
||||
public abstract Vector2 LinearVelocity { get; set; }
|
||||
public abstract float AngularVelocity { get; set; }
|
||||
public abstract float Mass { get; set; }
|
||||
public abstract Vector2 Momentum { get; set; }
|
||||
public abstract BodyStatus Status { get; set; }
|
||||
|
||||
public abstract bool OnGround { get; }
|
||||
|
||||
[CanBeNull]
|
||||
public abstract VirtualController Controller { get; }
|
||||
}
|
||||
[Serializable, NetSerializable]
|
||||
public enum BodyStatus
|
||||
{
|
||||
OnGround,
|
||||
InAir
|
||||
}
|
||||
}
|
||||
21
Robust.Shared/Physics/VirtualController.cs
Normal file
21
Robust.Shared/Physics/VirtualController.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
namespace Robust.Shared.Physics
|
||||
{
|
||||
/// <summary>
|
||||
/// The VirtualController allows dynamic changes in the properties of a physics component, usually to simulate a complex physical interaction (such as player movement).
|
||||
/// </summary>
|
||||
public abstract class VirtualController
|
||||
{
|
||||
public abstract SharedPhysicsComponent ControlledComponent { set; }
|
||||
|
||||
/// <summary>
|
||||
/// Modify a physics component before processing impulses
|
||||
/// </summary>
|
||||
public virtual void UpdateBeforeProcessing() { }
|
||||
|
||||
/// <summary>
|
||||
/// Modify a physics component after processing impulses
|
||||
/// </summary>
|
||||
public virtual void UpdateAfterProcessing() { }
|
||||
}
|
||||
}
|
||||
@@ -15,10 +15,10 @@ namespace Robust.UnitTesting.Shared.Physics
|
||||
private void SetupDefault()
|
||||
{
|
||||
A = new Mock<IPhysBody>();
|
||||
A.Setup(x => x.CollisionEnabled).Returns(true);
|
||||
A.Setup(x => x.CanCollide).Returns(true);
|
||||
|
||||
B = new Mock<IPhysBody>();
|
||||
B.Setup(x => x.CollisionEnabled).Returns(true);
|
||||
B.Setup(x => x.CanCollide).Returns(true);
|
||||
}
|
||||
|
||||
private void Act()
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using Moq;
|
||||
using NUnit.Framework;
|
||||
using Robust.Shared.GameObjects;
|
||||
@@ -23,15 +26,14 @@ namespace Robust.UnitTesting.Shared.Physics
|
||||
|
||||
var mock = new Mock<IPhysBody>();
|
||||
mock.Setup(foo => foo.WorldAABB).Returns(box);
|
||||
mock.Setup(foo => foo.IsHardCollidable).Returns(true);
|
||||
mock.Setup(foo => foo.MapID).Returns(new MapId(0));
|
||||
mock.Setup(foo => foo.CollisionEnabled).Returns(true);
|
||||
mock.Setup(foo => foo.CanCollide).Returns(true);
|
||||
mock.Setup(foo => foo.CollisionLayer).Returns(0x4);
|
||||
mock.Setup(foo => foo.CollisionMask).Returns(0x04);
|
||||
manager.AddBody(mock.Object);
|
||||
|
||||
// Act
|
||||
var result = manager.IsColliding(testBox, new MapId(0));
|
||||
var result = manager.TryCollideRect(testBox, new MapId(0));
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.False);
|
||||
@@ -47,44 +49,19 @@ namespace Robust.UnitTesting.Shared.Physics
|
||||
|
||||
var mock = new Mock<IPhysBody>();
|
||||
mock.Setup(foo => foo.WorldAABB).Returns(box);
|
||||
mock.Setup(foo => foo.IsHardCollidable).Returns(true);
|
||||
mock.Setup(foo => foo.MapID).Returns(new MapId(0));
|
||||
mock.Setup(foo => foo.CollisionEnabled).Returns(true);
|
||||
mock.Setup(foo => foo.CanCollide).Returns(true);
|
||||
mock.Setup(foo => foo.CollisionLayer).Returns(0x4);
|
||||
mock.Setup(foo => foo.CollisionMask).Returns(0x04);
|
||||
manager.AddBody(mock.Object);
|
||||
|
||||
// Act
|
||||
var result = manager.IsColliding(testBox, new MapId(0));
|
||||
var result = manager.TryCollideRect(testBox, new MapId(0));
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsCollidingNotHard()
|
||||
{
|
||||
// Arrange
|
||||
var box = new Box2(5, -5, 10, 6);
|
||||
var testBox = new Box2(-3, -3, 5, 6);
|
||||
var manager = new PhysicsManager();
|
||||
|
||||
var mock = new Mock<IPhysBody>();
|
||||
mock.Setup(foo => foo.WorldAABB).Returns(box);
|
||||
mock.Setup(foo => foo.IsHardCollidable).Returns(false);
|
||||
mock.Setup(foo => foo.MapID).Returns(new MapId(0));
|
||||
mock.Setup(foo => foo.CollisionEnabled).Returns(true);
|
||||
mock.Setup(foo => foo.CollisionLayer).Returns(0x4);
|
||||
mock.Setup(foo => foo.CollisionMask).Returns(0x04);
|
||||
manager.AddBody(mock.Object);
|
||||
|
||||
// Act
|
||||
var result = manager.IsColliding(testBox, new MapId(0));
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void IsCollidingTrue()
|
||||
{
|
||||
@@ -95,15 +72,14 @@ namespace Robust.UnitTesting.Shared.Physics
|
||||
|
||||
var mock = new Mock<IPhysBody>();
|
||||
mock.Setup(foo => foo.WorldAABB).Returns(box);
|
||||
mock.Setup(foo => foo.IsHardCollidable).Returns(true);
|
||||
mock.Setup(foo => foo.MapID).Returns(new MapId(0));
|
||||
mock.Setup(foo => foo.CollisionEnabled).Returns(true);
|
||||
mock.Setup(foo => foo.CanCollide).Returns(true);
|
||||
mock.Setup(foo => foo.CollisionLayer).Returns(0x4);
|
||||
mock.Setup(foo => foo.CollisionMask).Returns(0x04);
|
||||
manager.AddBody(mock.Object);
|
||||
|
||||
// Act
|
||||
var result = manager.IsColliding(testBox, new MapId(0));
|
||||
var result = manager.TryCollideRect(testBox, new MapId(0));
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.True);
|
||||
@@ -119,15 +95,14 @@ namespace Robust.UnitTesting.Shared.Physics
|
||||
|
||||
var mock = new Mock<IPhysBody>();
|
||||
mock.Setup(foo => foo.WorldAABB).Returns(box);
|
||||
mock.Setup(foo => foo.IsHardCollidable).Returns(true);
|
||||
mock.Setup(foo => foo.MapID).Returns(new MapId(0));
|
||||
mock.Setup(foo => foo.CollisionEnabled).Returns(true);
|
||||
mock.Setup(foo => foo.CanCollide).Returns(true);
|
||||
mock.Setup(foo => foo.CollisionLayer).Returns(0); // Collision layer is None
|
||||
mock.Setup(foo => foo.CollisionMask).Returns(0x04);
|
||||
manager.AddBody(mock.Object);
|
||||
|
||||
// Act
|
||||
var result = manager.IsColliding(testBox, new MapId(0));
|
||||
var result = manager.TryCollideRect(testBox, new MapId(0));
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.False);
|
||||
@@ -143,15 +118,14 @@ namespace Robust.UnitTesting.Shared.Physics
|
||||
|
||||
var mock = new Mock<IPhysBody>();
|
||||
mock.Setup(foo => foo.WorldAABB).Returns(box);
|
||||
mock.Setup(foo => foo.IsHardCollidable).Returns(true);
|
||||
mock.Setup(foo => foo.MapID).Returns(new MapId(3));
|
||||
mock.Setup(foo => foo.CollisionEnabled).Returns(true);
|
||||
mock.Setup(foo => foo.CanCollide).Returns(true);
|
||||
mock.Setup(foo => foo.CollisionLayer).Returns(0x4);
|
||||
mock.Setup(foo => foo.CollisionMask).Returns(0x04);
|
||||
manager.AddBody(mock.Object);
|
||||
|
||||
// Act
|
||||
var result = manager.IsColliding(testBox, new MapId(0));
|
||||
var result = manager.TryCollideRect(testBox, new MapId(0));
|
||||
|
||||
// Assert
|
||||
Assert.That(result, Is.False);
|
||||
@@ -168,17 +142,19 @@ namespace Robust.UnitTesting.Shared.Physics
|
||||
var mock = new Mock<IPhysBody>();
|
||||
mock.Setup(foo => foo.WorldAABB).Returns(box);
|
||||
mock.Setup(foo => foo.Owner).Returns(new Entity()); // requires IPhysBody not have null owner
|
||||
mock.Setup(foo => foo.CollisionEnabled).Returns(true);
|
||||
mock.Setup(foo => foo.CanCollide).Returns(true);
|
||||
mock.Setup(foo => foo.CollisionLayer).Returns(1);
|
||||
mock.Setup(foo => foo.CollisionMask).Returns(1);
|
||||
mock.Setup(foo => foo.IsHardCollidable).Returns(true);
|
||||
manager.AddBody(mock.Object);
|
||||
|
||||
// Act
|
||||
var result = manager.IntersectRay(new MapId(0), ray);
|
||||
var results = manager.IntersectRay(new MapId(0), ray).ToList();
|
||||
|
||||
Assert.That(results.Count, Is.EqualTo(1));
|
||||
|
||||
var result = results.First();
|
||||
|
||||
// Assert
|
||||
Assert.That(result.DidHitObject, Is.True);
|
||||
Assert.That(result.Distance, Is.EqualTo(5));
|
||||
Assert.That(result.HitPos.X, Is.EqualTo(5));
|
||||
Assert.That(result.HitPos.Y, Is.EqualTo(1));
|
||||
@@ -195,25 +171,63 @@ namespace Robust.UnitTesting.Shared.Physics
|
||||
var mock = new Mock<IPhysBody>();
|
||||
mock.Setup(foo => foo.WorldAABB).Returns(box);
|
||||
mock.Setup(foo => foo.Owner).Returns(new Entity()); // requires IPhysBody not have null owner
|
||||
mock.Setup(foo => foo.CollisionEnabled).Returns(true);
|
||||
mock.Setup(foo => foo.CanCollide).Returns(true);
|
||||
mock.Setup(foo => foo.CollisionLayer).Returns(1);
|
||||
mock.Setup(foo => foo.CollisionMask).Returns(1);
|
||||
manager.AddBody(mock.Object);
|
||||
|
||||
// Act
|
||||
var result = manager.IntersectRay(new MapId(0), ray);
|
||||
var results = manager.IntersectRay(new MapId(0), ray);
|
||||
|
||||
// Assert
|
||||
Assert.That(result.DidHitObject, Is.False);
|
||||
Assert.That(result.Distance, Is.EqualTo(0.0f));
|
||||
Assert.That(result.HitPos, Is.EqualTo(Vector2.Zero));
|
||||
Assert.That(results.Count(), Is.EqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void MultiHitRayCast()
|
||||
{
|
||||
// Arrange
|
||||
var b1 = new Box2(5, -5, 10, 6);
|
||||
var b2 = new Box2(6, -10, 7, 10);
|
||||
var ray = new CollisionRay(Vector2.UnitY, Vector2.UnitX, 1);
|
||||
var manager = new PhysicsManager();
|
||||
|
||||
var e1 = new Entity();
|
||||
var e2 = new Entity();
|
||||
|
||||
var m1 = new Mock<IPhysBody>();
|
||||
m1.Setup(foo => foo.WorldAABB).Returns(b1);
|
||||
m1.Setup(foo => foo.Owner).Returns(e1);
|
||||
m1.Setup(foo => foo.CanCollide).Returns(true);
|
||||
m1.Setup(foo => foo.CollisionLayer).Returns(1);
|
||||
m1.Setup(foo => foo.CollisionMask).Returns(1);
|
||||
manager.AddBody(m1.Object);
|
||||
|
||||
var m2 = new Mock<IPhysBody>();
|
||||
m2.Setup(foo => foo.WorldAABB).Returns(b2);
|
||||
m2.Setup(foo => foo.Owner).Returns(e2);
|
||||
m2.Setup(foo => foo.CanCollide).Returns(true);
|
||||
m2.Setup(foo => foo.CollisionLayer).Returns(1);
|
||||
m2.Setup(foo => foo.CollisionMask).Returns(1);
|
||||
manager.AddBody(m2.Object);
|
||||
|
||||
var results = manager.IntersectRay(new MapId(0), ray, returnOnFirstHit: false).ToList();
|
||||
|
||||
Assert.That(results.Count, Is.EqualTo(2));
|
||||
Assert.That(results[0].HitEntity.Uid, Is.EqualTo(e1.Uid));
|
||||
Assert.That(results[1].HitEntity.Uid, Is.EqualTo(e2.Uid));
|
||||
Assert.That(results[0].Distance, Is.EqualTo(5));
|
||||
Assert.That(results[0].HitPos.X, Is.EqualTo(5));
|
||||
Assert.That(results[0].HitPos.Y, Is.EqualTo(1));
|
||||
Assert.That(results[1].Distance, Is.EqualTo(6));
|
||||
Assert.That(results[1].HitPos.X, Is.EqualTo(6));
|
||||
Assert.That(results[1].HitPos.Y, Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DoCollisionTestTrue()
|
||||
{
|
||||
// Arrange
|
||||
var results = new List<IPhysBody>(1);
|
||||
var manager = new PhysicsManager();
|
||||
|
||||
var mockEntity0 = new Mock<IEntity>().Object;
|
||||
@@ -221,9 +235,8 @@ namespace Robust.UnitTesting.Shared.Physics
|
||||
|
||||
var mock0 = new Mock<IPhysBody>();
|
||||
mock0.Setup(foo => foo.WorldAABB).Returns(new Box2(-3, -3, 6, 6));
|
||||
mock0.Setup(foo => foo.IsHardCollidable).Returns(true);
|
||||
mock0.Setup(foo => foo.MapID).Returns(new MapId(1));
|
||||
mock0.Setup(foo => foo.CollisionEnabled).Returns(true);
|
||||
mock0.Setup(foo => foo.CanCollide).Returns(true);
|
||||
mock0.Setup(foo => foo.CollisionLayer).Returns(0x4);
|
||||
mock0.Setup(foo => foo.CollisionMask).Returns(0x04);
|
||||
mock0.Setup(foo => foo.Owner).Returns(mockEntity0);
|
||||
@@ -232,9 +245,8 @@ namespace Robust.UnitTesting.Shared.Physics
|
||||
|
||||
var mock1 = new Mock<IPhysBody>();
|
||||
mock1.Setup(foo => foo.WorldAABB).Returns(new Box2(5, -5, 10, 6));
|
||||
mock1.Setup(foo => foo.IsHardCollidable).Returns(true);
|
||||
mock1.Setup(foo => foo.MapID).Returns(new MapId(1));
|
||||
mock1.Setup(foo => foo.CollisionEnabled).Returns(true);
|
||||
mock1.Setup(foo => foo.CanCollide).Returns(true);
|
||||
mock1.Setup(foo => foo.CollisionLayer).Returns(0x4);
|
||||
mock1.Setup(foo => foo.CollisionMask).Returns(0x04);
|
||||
mock1.Setup(foo => foo.Owner).Returns(mockEntity1);
|
||||
@@ -242,11 +254,11 @@ namespace Robust.UnitTesting.Shared.Physics
|
||||
manager.AddBody(testBody);
|
||||
|
||||
// Act
|
||||
manager.DoCollisionTest(testBody, testBody.WorldAABB, results);
|
||||
var results = manager.GetCollidingEntities(testBody, Vector2.Zero).ToImmutableList();
|
||||
|
||||
// Assert
|
||||
Assert.That(results.Count, Is.EqualTo(1));
|
||||
Assert.That(results[0], Is.EqualTo(staticBody));
|
||||
Assert.That(results[0], Is.EqualTo(mockEntity0));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user