mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-06 17:58:05 +02:00
649 lines
22 KiB
C#
649 lines
22 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Numerics;
|
|
using Robust.Shared.Containers;
|
|
using Robust.Shared.GameObjects;
|
|
using Robust.Shared.IoC;
|
|
using Robust.Shared.Map;
|
|
using Robust.Shared.Physics.Components;
|
|
using Robust.Shared.Physics.Dynamics.Contacts;
|
|
using Robust.Shared.Physics.Dynamics.Joints;
|
|
using Robust.Shared.Timing;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Robust.Shared.Physics.Systems;
|
|
|
|
public abstract partial class SharedJointSystem : EntitySystem
|
|
{
|
|
[Dependency] private readonly SharedContainerSystem _container = default!;
|
|
[Dependency] private readonly SharedPhysicsSystem _physics = default!;
|
|
[Dependency] private readonly IGameTiming _gameTiming = default!;
|
|
[Dependency] private readonly SharedTransformSystem _transform = default!;
|
|
|
|
private EntityQuery<JointComponent> _jointsQuery;
|
|
private EntityQuery<PhysicsComponent> _physicsQuery;
|
|
private EntityQuery<JointRelayTargetComponent> _relayQuery;
|
|
|
|
// To avoid issues with component states we'll queue up all dirty joints and check it every tick to see if
|
|
// we can delete the component.
|
|
private readonly HashSet<Entity<JointComponent>> _dirtyJoints = new();
|
|
protected readonly HashSet<Joint> AddedJoints = new();
|
|
protected readonly List<Joint> ToRemove = new();
|
|
|
|
public override void Initialize()
|
|
{
|
|
base.Initialize();
|
|
|
|
_jointsQuery = GetEntityQuery<JointComponent>();
|
|
_relayQuery = GetEntityQuery<JointRelayTargetComponent>();
|
|
_physicsQuery = GetEntityQuery<PhysicsComponent>();
|
|
UpdatesOutsidePrediction = true;
|
|
|
|
UpdatesBefore.Add(typeof(SharedPhysicsSystem));
|
|
SubscribeLocalEvent<JointComponent, ComponentShutdown>(OnJointShutdown);
|
|
SubscribeLocalEvent<JointComponent, ComponentInit>(OnJointInit);
|
|
|
|
InitializeRelay();
|
|
}
|
|
|
|
#region Lifetime
|
|
|
|
private void OnJointInit(EntityUid uid, JointComponent component, ComponentInit args)
|
|
{
|
|
foreach (var (id, joint) in component.Joints)
|
|
{
|
|
var other = uid == joint.BodyAUid ? joint.BodyBUid : joint.BodyAUid;
|
|
|
|
// Client may not yet know about the other entity.
|
|
// But whenever that other entity enters PVS, its own joint initialization should hopefully run this again anyways.
|
|
if (!TryComp(joint.BodyAUid, out PhysicsComponent? bodyA) || !TryComp(joint.BodyBUid, out PhysicsComponent? bodyB) || !TryComp(other, out JointComponent? otherComp))
|
|
continue;
|
|
|
|
if (!otherComp.Joints.ContainsKey(id))
|
|
{
|
|
// This can happen if the other joint handled its state before this entity was initialized. In this
|
|
// case we need to re-add the joint to the other entity.
|
|
if (uid == joint.BodyAUid)
|
|
InitJoint(joint, bodyA, bodyB, component, otherComp, ignoreExisting: true);
|
|
else
|
|
InitJoint(joint, bodyA, bodyB, otherComp, component, ignoreExisting: true);
|
|
continue;
|
|
}
|
|
|
|
_physics.WakeBody(joint.BodyAUid, body: bodyA);
|
|
_physics.WakeBody(joint.BodyBUid, body: bodyB);
|
|
|
|
// Raise broadcast last so we can do both sides of directed first.
|
|
var vera = new JointAddedEvent(joint, joint.BodyAUid, joint.BodyBUid, bodyA, bodyB);
|
|
RaiseLocalEvent(joint.BodyAUid, vera);
|
|
var smug = new JointAddedEvent(joint, joint.BodyBUid, joint.BodyAUid, bodyB, bodyA);
|
|
RaiseLocalEvent(joint.BodyBUid, smug);
|
|
EntityManager.EventBus.RaiseEvent(EventSource.Local, vera);
|
|
}
|
|
|
|
RefreshRelay(uid, component);
|
|
}
|
|
|
|
private void OnJointShutdown(EntityUid uid, JointComponent component, ComponentShutdown args)
|
|
{
|
|
foreach (var joint in component.Joints.Values)
|
|
{
|
|
RemoveJoint(joint);
|
|
}
|
|
|
|
// If we're relaying elsewhere then cleanup our old data.
|
|
if (component.Relay != null && !TerminatingOrDeleted(component.Relay.Value))
|
|
SetRelay(uid, null, component);
|
|
}
|
|
|
|
#endregion
|
|
|
|
public void SetEnabled(Joint joint, bool value)
|
|
{
|
|
if (joint.Enabled == value)
|
|
return;
|
|
|
|
joint.Enabled = value;
|
|
}
|
|
|
|
public override void Update(float frameTime)
|
|
{
|
|
base.Update(frameTime);
|
|
|
|
foreach (var joint in AddedJoints)
|
|
{
|
|
InitJoint(joint);
|
|
}
|
|
|
|
AddedJoints.Clear();
|
|
|
|
foreach (var joint in _dirtyJoints)
|
|
{
|
|
if (joint.Comp.Deleted || joint.Comp.JointCount != 0) continue;
|
|
RemComp<JointComponent>(joint);
|
|
}
|
|
|
|
_dirtyJoints.Clear();
|
|
}
|
|
|
|
private void InitJoint(Joint joint,
|
|
PhysicsComponent? bodyA = null,
|
|
PhysicsComponent? bodyB = null,
|
|
JointComponent? jointComponentA = null,
|
|
JointComponent? jointComponentB = null,
|
|
bool ignoreExisting = false)
|
|
{
|
|
var aUid = joint.BodyAUid;
|
|
var bUid = joint.BodyBUid;
|
|
|
|
if (!_physicsQuery.Resolve(aUid, ref bodyA, false) || !_physicsQuery.Resolve(bUid, ref bodyB, false))
|
|
return;
|
|
|
|
DebugTools.Assert(Transform(aUid).MapID == Transform(bUid).MapID, "Attempted to initialize cross-map joint");
|
|
|
|
jointComponentA ??= EnsureComp<JointComponent>(aUid);
|
|
jointComponentB ??= EnsureComp<JointComponent>(bUid);
|
|
DebugTools.AssertOwner(aUid, jointComponentA);
|
|
DebugTools.AssertOwner(bUid, jointComponentB);
|
|
DebugTools.AssertNotEqual(jointComponentA.Relay, bUid);
|
|
DebugTools.AssertNotEqual(jointComponentB.Relay, aUid);
|
|
|
|
var jointsA = jointComponentA.Joints;
|
|
var jointsB = jointComponentB.Joints;
|
|
|
|
if (_gameTiming.IsFirstTimePredicted)
|
|
Log.Debug($"Initializing joint {joint.ID}");
|
|
|
|
// Check for existing joints
|
|
if (!ignoreExisting && jointsA.TryGetValue(joint.ID, out var existing))
|
|
{
|
|
if (existing.BodyBUid != bUid)
|
|
{
|
|
Log.Error($"While adding joint {joint.ID} to entity {ToPrettyString(bUid)}, the connected entity {ToPrettyString(aUid)} already had a joint with the same ID connected to another entity {ToPrettyString(existing.BodyBUid)}.");
|
|
return;
|
|
}
|
|
|
|
// If they both already have it we should be gucci
|
|
// This can occur because of client states coming in blah blah
|
|
// The reason for this is we defer everything until Update
|
|
// (and the reason we defer is to avoid modifying components during iteration when we do the EnsureComponent)
|
|
if (jointsB.TryGetValue(joint.ID, out var value))
|
|
{
|
|
DebugTools.Assert(value.BodyAUid == aUid);
|
|
return;
|
|
}
|
|
|
|
Log.Error($"While adding joint {joint.ID} to entity {ToPrettyString(bUid)}, the joint already existed for the connected entity {ToPrettyString(aUid)}.");
|
|
}
|
|
else if (!ignoreExisting && jointsB.TryGetValue(joint.ID, out existing))
|
|
{
|
|
if (existing.BodyAUid != aUid)
|
|
{
|
|
Log.Error($"While adding joint {joint.ID} to entity {ToPrettyString(aUid)}, the connected entity {ToPrettyString(bUid)} already had a joint with the same ID connected to another entity {ToPrettyString(existing.BodyAUid)}.");
|
|
return;
|
|
}
|
|
|
|
Log.Error($"While adding joint {joint.ID} to entity {ToPrettyString(aUid)}, the joint already existed for the connected entity {ToPrettyString(bUid)}.");
|
|
}
|
|
|
|
jointsA.TryAdd(joint.ID, joint);
|
|
jointsB.TryAdd(joint.ID, joint);
|
|
|
|
// If the joint prevents collisions, then flag any contacts for filtering.
|
|
if (!joint.CollideConnected)
|
|
{
|
|
FilterContactsForJoint(joint, bodyA, bodyB);
|
|
}
|
|
|
|
// TODO reduce metadata resolves.
|
|
_physics.WakeBody(aUid, body: bodyA);
|
|
_physics.WakeBody(bUid, body: bodyB);
|
|
Dirty(aUid, bodyA);
|
|
Dirty(bUid, bodyB);
|
|
Dirty(aUid, jointComponentA);
|
|
Dirty(bUid, jointComponentB);
|
|
|
|
// Also flag these for checking juusssttt in case.
|
|
_dirtyJoints.Add((aUid, jointComponentA));
|
|
_dirtyJoints.Add((bUid, jointComponentB));
|
|
// Note: creating a joint doesn't wake the bodies.
|
|
|
|
// Raise broadcast last so we can do both sides of directed first.
|
|
var vera = new JointAddedEvent(joint, aUid, bUid, bodyA, bodyB);
|
|
EntityManager.EventBus.RaiseLocalEvent(aUid, vera);
|
|
var smug = new JointAddedEvent(joint, bUid, aUid, bodyB, bodyA);
|
|
EntityManager.EventBus.RaiseLocalEvent(bUid, smug);
|
|
EntityManager.EventBus.RaiseEvent(EventSource.Local, vera);
|
|
}
|
|
|
|
private static string GetJointId(Joint joint)
|
|
{
|
|
var id = joint.ID;
|
|
return !string.IsNullOrEmpty(id) ? id : joint.GetHashCode().ToString();
|
|
}
|
|
|
|
#region Helpers
|
|
|
|
/// <summary>
|
|
/// Create a DistanceJoint between 2 bodies. This should be called content-side whenever you need one.
|
|
/// </summary>
|
|
public DistanceJoint CreateDistanceJoint(
|
|
EntityUid bodyA,
|
|
EntityUid bodyB,
|
|
Vector2? anchorA = null,
|
|
Vector2? anchorB = null,
|
|
string? id = null,
|
|
TransformComponent? xformA = null,
|
|
TransformComponent? xformB = null,
|
|
float? minimumDistance = null)
|
|
{
|
|
if (!Resolve(bodyA, ref xformA) || !Resolve(bodyB, ref xformB))
|
|
{
|
|
throw new InvalidOperationException();
|
|
}
|
|
|
|
anchorA ??= Vector2.Zero;
|
|
anchorB ??= Vector2.Zero;
|
|
|
|
var vecA = Vector2.Transform(anchorA.Value, _transform.GetWorldMatrix(xformA));
|
|
var vecB = Vector2.Transform(anchorB.Value, _transform.GetWorldMatrix(xformB));
|
|
var length = (vecA - vecB).Length();
|
|
if (minimumDistance != null)
|
|
length = Math.Max(minimumDistance.Value, length);
|
|
|
|
var joint = new DistanceJoint(bodyA, bodyB, anchorA.Value, anchorB.Value, length);
|
|
id ??= GetJointId(joint);
|
|
joint.ID = id;
|
|
AddJoint(joint);
|
|
|
|
return joint;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Create a MouseJoint between 2 bodies. This should be called content-side whenever you need one.
|
|
/// </summary>
|
|
public MouseJoint CreateMouseJoint(EntityUid bodyA, EntityUid bodyB, Vector2? anchorA = null, Vector2? anchorB = null, string? id = null)
|
|
{
|
|
anchorA ??= Vector2.Zero;
|
|
anchorB ??= Vector2.Zero;
|
|
|
|
var joint = new MouseJoint(bodyA, bodyB, anchorA.Value, anchorB.Value);
|
|
id ??= GetJointId(joint);
|
|
joint.ID = id;
|
|
AddJoint(joint);
|
|
|
|
return joint;
|
|
}
|
|
|
|
public PrismaticJoint CreatePrismaticJoint(EntityUid bodyA, EntityUid bodyB, string? id = null)
|
|
{
|
|
var joint = new PrismaticJoint(bodyA, bodyB);
|
|
id ??= GetJointId(joint);
|
|
joint.ID = id;
|
|
AddJoint(joint);
|
|
|
|
return joint;
|
|
}
|
|
|
|
public PrismaticJoint CreatePrismaticJoint(
|
|
EntityUid bodyA,
|
|
EntityUid bodyB,
|
|
Vector2 anchorA,
|
|
Vector2 anchorB,
|
|
Vector2 worldAxis,
|
|
float referenceAngle,
|
|
string? id = null)
|
|
{
|
|
var axis = GetLocalVector2(bodyA, worldAxis);
|
|
var joint = new PrismaticJoint(bodyA, bodyB, anchorA, anchorB, axis, referenceAngle);
|
|
id ??= GetJointId(joint);
|
|
joint.ID = id;
|
|
AddJoint(joint);
|
|
|
|
return joint;
|
|
}
|
|
|
|
public RevoluteJoint CreateRevoluteJoint(EntityUid bodyA, EntityUid bodyB, string? id = null)
|
|
{
|
|
var joint = new RevoluteJoint(bodyA, bodyB);
|
|
id ??= GetJointId(joint);
|
|
joint.ID = id;
|
|
AddJoint(joint);
|
|
|
|
return joint;
|
|
}
|
|
|
|
public WeldJoint GetOrCreateWeldJoint(EntityUid bodyA, EntityUid bodyB, string? id = null)
|
|
{
|
|
if (id != null &&
|
|
_jointsQuery.TryComp(bodyA, out JointComponent? jointComponent) &&
|
|
jointComponent.Joints.TryGetValue(id, out var weldJoint))
|
|
{
|
|
return (WeldJoint) weldJoint;
|
|
}
|
|
|
|
var joint = new WeldJoint(bodyA, bodyB);
|
|
id ??= GetJointId(joint);
|
|
joint.ID = id;
|
|
AddJoint(joint);
|
|
|
|
return joint;
|
|
}
|
|
|
|
public WeldJoint CreateWeldJoint(EntityUid bodyA, EntityUid bodyB, string? id = null)
|
|
{
|
|
var joint = new WeldJoint(bodyA, bodyB);
|
|
id ??= GetJointId(joint);
|
|
joint.ID = id;
|
|
AddJoint(joint);
|
|
|
|
return joint;
|
|
}
|
|
|
|
private Vector2 GetLocalVector2(EntityUid uid, Vector2 worldVector, TransformComponent? xform = null)
|
|
{
|
|
if (!Resolve(uid, ref xform))
|
|
return Vector2.Zero;
|
|
|
|
return Physics.Transform.MulT(new Quaternion2D((float) _transform.GetWorldRotation(xform).Theta), worldVector);
|
|
}
|
|
|
|
#endregion
|
|
|
|
public static void LinearStiffness(
|
|
float frequencyHertz,
|
|
float dampingRatio,
|
|
float massA,
|
|
float massB,
|
|
out float stiffness, out float damping)
|
|
{
|
|
float mass;
|
|
if (massA > 0.0f && massB > 0.0f)
|
|
{
|
|
mass = massA * massB / (massA + massB);
|
|
}
|
|
else if (massA > 0.0f)
|
|
{
|
|
mass = massA;
|
|
}
|
|
else
|
|
{
|
|
mass = massB;
|
|
}
|
|
|
|
var omega = 2.0f * MathF.PI * frequencyHertz;
|
|
stiffness = mass * omega * omega;
|
|
damping = 2.0f * mass * dampingRatio * omega;
|
|
}
|
|
|
|
public static void AngularStiffness(
|
|
float frequencyHertz,
|
|
float dampingRatio,
|
|
PhysicsComponent bodyA,
|
|
PhysicsComponent bodyB,
|
|
out float stiffness, out float damping)
|
|
{
|
|
var IA = bodyA.Inertia;
|
|
var IB = bodyB.Inertia;
|
|
|
|
float I;
|
|
if (IA > 0.0f && IB > 0.0f)
|
|
{
|
|
I = IA * IB / (IA + IB);
|
|
}
|
|
else if (IA > 0.0f)
|
|
{
|
|
I = IA;
|
|
}
|
|
else
|
|
{
|
|
I = IB;
|
|
}
|
|
|
|
float omega = 2.0f * MathF.PI * frequencyHertz;
|
|
stiffness = I * omega * omega;
|
|
damping = 2.0f * I * dampingRatio * omega;
|
|
}
|
|
|
|
#region Joints
|
|
|
|
protected void AddJoint(Joint joint, PhysicsComponent? bodyA = null, PhysicsComponent? bodyB = null)
|
|
{
|
|
if (!_physicsQuery.Resolve(joint.BodyAUid, ref bodyA) || !_physicsQuery.Resolve(joint.BodyBUid, ref bodyB))
|
|
return;
|
|
|
|
if (!joint.CollideConnected)
|
|
FilterContactsForJoint(joint, bodyA, bodyB);
|
|
|
|
// Maybe make this method AddOrUpdate so we can have an Add one that explicitly throws if present?
|
|
var mapidA = Transform(joint.BodyAUid).MapID;
|
|
|
|
if (mapidA == MapId.Nullspace ||
|
|
mapidA != Transform(joint.BodyBUid).MapID)
|
|
{
|
|
Log.Error($"Tried to add joint to ineligible bodies");
|
|
return;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(joint.ID))
|
|
{
|
|
Log.Error($"Can't add a joint with no ID");
|
|
DebugTools.Assert($"Can't add a joint with no ID");
|
|
return;
|
|
}
|
|
|
|
InitJoint(joint, bodyA, bodyB);
|
|
|
|
if (_gameTiming.IsFirstTimePredicted)
|
|
{
|
|
Log.Debug($"Added {joint.JointType} Joint with ID {joint.ID} from {bodyA.BodyType} to {bodyB.BodyType} ");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes joints on this entity and anything relaying to it.
|
|
/// </summary>
|
|
public void RecursiveClearJoints(
|
|
EntityUid uid,
|
|
TransformComponent? xform = null,
|
|
JointComponent? component = null,
|
|
JointRelayTargetComponent? relay = null)
|
|
{
|
|
if (!Resolve(uid, ref xform))
|
|
return;
|
|
|
|
_jointsQuery.Resolve(uid, ref component, false);
|
|
_relayQuery.Resolve(uid, ref relay, false);
|
|
|
|
if (relay != null)
|
|
{
|
|
foreach (var ree in relay.Relayed)
|
|
{
|
|
_jointsQuery.TryGetComponent(ree, out var rJoint);
|
|
ClearJoints(ree, rJoint);
|
|
}
|
|
|
|
RemComp(uid, relay);
|
|
}
|
|
|
|
if (component != null)
|
|
{
|
|
ClearJoints(uid, component);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Clears any joints for this entity, excluding relayed joints.
|
|
/// </summary>
|
|
public void ClearJoints(EntityUid uid, JointComponent? component = null)
|
|
{
|
|
if (!_jointsQuery.Resolve(uid, ref component, false))
|
|
return;
|
|
|
|
// TODO PERFORMANCE
|
|
// This will re-fetch the joint & body component for this entity ( & ever connected
|
|
// entity), for each and every joint. at the very least, we could pass in the joint & physics comp. As long
|
|
// as most entities only have a single joint, fetching connected components probably isn't worth it.
|
|
foreach (var a in component.Joints.Values.ToArray())
|
|
{
|
|
RemoveJoint(a);
|
|
}
|
|
|
|
foreach (var j in AddedJoints)
|
|
{
|
|
if (j.BodyAUid == uid || j.BodyBUid == uid)
|
|
ToRemove.Add(j);
|
|
}
|
|
AddedJoints.ExceptWith(ToRemove);
|
|
ToRemove.Clear();
|
|
|
|
if (_gameTiming.IsFirstTimePredicted)
|
|
{
|
|
Log.Debug($"Removed all joints from entity {ToPrettyString(uid)}");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes any joints attached to this object with a specified ID.
|
|
/// </summary>
|
|
/// <param name="uid">Entity UID of the object to remove the joint from</param>
|
|
/// <param name="id">ID of the joint to remove</param>
|
|
/// <param name="recursive">Whether to also remove any relayed joints. Optional, defaults to true</param>
|
|
public void RemoveJoint(EntityUid uid, string id, bool recursive = true)
|
|
{
|
|
if (_jointsQuery.TryComp(uid, out var jointComp) &&
|
|
jointComp.Joints.TryGetValue(id, out var joint))
|
|
RemoveJoint(joint);
|
|
|
|
if (!recursive || !_relayQuery.TryComp(uid, out var relayComp))
|
|
return;
|
|
|
|
foreach (var relay in relayComp.Relayed)
|
|
{
|
|
if(_jointsQuery.TryComp(relay, out var relayJointComp))
|
|
{
|
|
if(relayJointComp.Joints.TryGetValue(id, out var relayJoint))
|
|
RemoveJoint(relayJoint);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a specific joint from existence
|
|
/// </summary>
|
|
/// <param name="joint">The joint to remove</param>
|
|
public void RemoveJoint(Joint joint)
|
|
{
|
|
AddedJoints.Remove(joint);
|
|
var bodyAUid = joint.BodyAUid;
|
|
var bodyBUid = joint.BodyBUid;
|
|
|
|
// Originally I logged these but because of prediction the client can just nuke them multiple times in a row
|
|
// because each body has its own JointComponent, bleh.
|
|
if (!_jointsQuery.TryComp(bodyAUid, out var jointComponentA))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!_jointsQuery.TryComp(bodyBUid, out var jointComponentB))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!jointComponentA.Joints.Remove(joint.ID))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!jointComponentB.Joints.Remove(joint.ID))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Wake up connected bodies.
|
|
if (_physicsQuery.TryComp(bodyAUid, out var bodyA) &&
|
|
MetaData(bodyAUid).EntityLifeStage < EntityLifeStage.Terminating)
|
|
{
|
|
var uidA = jointComponentA.Relay ?? bodyAUid;
|
|
_physics.WakeBody(uidA);
|
|
}
|
|
|
|
if (TryComp<PhysicsComponent>(bodyBUid, out var bodyB) &&
|
|
MetaData(bodyBUid).EntityLifeStage < EntityLifeStage.Terminating)
|
|
{
|
|
var uidB = jointComponentB.Relay ?? bodyBUid;
|
|
_physics.WakeBody(uidB);
|
|
}
|
|
|
|
if (!jointComponentA.Deleted)
|
|
{
|
|
Dirty(bodyAUid, jointComponentA);
|
|
}
|
|
|
|
if (!jointComponentB.Deleted)
|
|
{
|
|
Dirty(bodyBUid, jointComponentB);
|
|
}
|
|
|
|
if (jointComponentA.Deleted && jointComponentB.Deleted)
|
|
return;
|
|
|
|
// If the joint prevents collisions, then flag any contacts for filtering.
|
|
if (!joint.CollideConnected)
|
|
{
|
|
FilterContactsForJoint(joint);
|
|
}
|
|
|
|
if (bodyA == null)
|
|
{
|
|
Log.Debug($"Tried to remove joint from entity {ToPrettyString(bodyAUid)} without a physics component");
|
|
}
|
|
else if (bodyB == null)
|
|
{
|
|
Log.Debug($"Tried to remove joint from entity {ToPrettyString(bodyBUid)} without a physics component");
|
|
}
|
|
else
|
|
{
|
|
var vera = new JointRemovedEvent(joint, bodyAUid, bodyBUid, bodyA, bodyB);
|
|
EntityManager.EventBus.RaiseLocalEvent(bodyAUid, vera, false);
|
|
var smug = new JointRemovedEvent(joint, bodyBUid, bodyAUid, bodyB, bodyA);
|
|
EntityManager.EventBus.RaiseLocalEvent(bodyBUid, smug, false);
|
|
EntityManager.EventBus.RaiseEvent(EventSource.Local, vera);
|
|
|
|
if (_gameTiming.IsFirstTimePredicted)
|
|
{
|
|
Log.Debug($"Removed {joint.JointType} joint with ID {joint.ID} from entity {ToPrettyString(bodyAUid)} to entity {ToPrettyString(bodyBUid)}");
|
|
}
|
|
}
|
|
|
|
// We can't just check up front due to how prediction works.
|
|
_dirtyJoints.Add((bodyAUid, jointComponentA));
|
|
_dirtyJoints.Add((bodyBUid, jointComponentB));
|
|
}
|
|
|
|
#endregion
|
|
|
|
internal void FilterContactsForJoint(Joint joint, PhysicsComponent? bodyA = null, PhysicsComponent? bodyB = null)
|
|
{
|
|
if (!_physicsQuery.Resolve(joint.BodyBUid, ref bodyB))
|
|
return;
|
|
|
|
var node = bodyB.Contacts.First;
|
|
|
|
while (node != null)
|
|
{
|
|
var contact = node.Value;
|
|
node = node.Next;
|
|
|
|
if (contact.EntityA == joint.BodyAUid ||
|
|
contact.EntityB == joint.BodyAUid)
|
|
{
|
|
// Flag the contact for filtering at the next time step (where either
|
|
// body is awake).
|
|
contact.Flags |= ContactFlags.Filter;
|
|
}
|
|
}
|
|
}
|
|
}
|