mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-14 19:29:36 +01:00
Pre-reqs for multithreaded physics (#1784)
* Tanks off guard * Medic * Diamondback * Gaming * Gamingo * Finalise * Doh * Bullet licence * Marginal cleanup * Physics licences * Apply reviews
This commit is contained in:
@@ -23,6 +23,48 @@
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
- name: Box2D
|
||||
license:
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Erin Catto
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
- name: Bullet Physics SDK
|
||||
license:
|
||||
The files in this repository are licensed under the zlib license, except for the files under 'Extras' and examples/ThirdPartyLibs.
|
||||
|
||||
Bullet Continuous Collision Detection and Physics Library
|
||||
http://bulletphysics.org
|
||||
|
||||
This software is provided 'as-is', without any express or implied warranty.
|
||||
In no event will the authors be held liable for any damages arising from the use of this software.
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it freely,
|
||||
subject to the following restrictions:
|
||||
|
||||
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
|
||||
If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
|
||||
- name: Castle Core
|
||||
license: |
|
||||
Copyright 2004-2016 Castle Project - http://www.castleproject.org/
|
||||
@@ -317,6 +359,43 @@
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
- name: Farseer Physics Engine
|
||||
license:
|
||||
Microsoft Permissive License (Ms-PL)
|
||||
|
||||
This license governs use of the accompanying software.
|
||||
If you use the software, you accept this license.
|
||||
If you do not accept the license, do not use the software.
|
||||
|
||||
1. Definitions
|
||||
The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law.
|
||||
A "contribution" is the original software, or any additions or changes to the software.
|
||||
A "contributor" is any person that distributes its contribution under this license.
|
||||
"Licensed patents" are a contributor's patent claims that read directly on its contribution.
|
||||
|
||||
2. Grant of Rights
|
||||
(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
|
||||
each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution,
|
||||
prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create.
|
||||
(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3,
|
||||
each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to
|
||||
make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or
|
||||
derivative works of the contribution in the software.
|
||||
|
||||
3. Conditions and Limitations
|
||||
(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks.
|
||||
(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software,
|
||||
your patent license from such contributor to the software ends automatically.
|
||||
(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark,
|
||||
and attribution notices that are present in the software.
|
||||
(D) If you distribute any portion of the software in source code form, you may do so only under this license by
|
||||
including a complete copy of this license with your distribution. If you distribute any portion of the software in
|
||||
compiled or object code form, you may only do so under a license that complies with this license.
|
||||
(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties,
|
||||
guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change.
|
||||
To the extent permitted under your local laws, the contributors exclude the implied warranties of
|
||||
merchantability, fitness for a particular purpose and non-infringement.
|
||||
|
||||
- name: Mono.Cecil
|
||||
license: |
|
||||
Copyright (c) 2008 - 2015 Jb Evain
|
||||
|
||||
@@ -62,8 +62,9 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
/// <summary>
|
||||
/// Store the body's index within the island so we can lookup its data.
|
||||
/// Key is Island's ID and value is our index.
|
||||
/// </summary>
|
||||
public int IslandIndex { get; set; }
|
||||
public Dictionary<int, int> IslandIndex { get; set; } = new();
|
||||
|
||||
// TODO: Actually implement after the initial pr dummy
|
||||
/// <summary>
|
||||
|
||||
@@ -108,6 +108,8 @@ namespace Robust.Shared.GameObjects
|
||||
SubscribeLocalEvent<EntRemovedFromContainerMessage>(HandleContainerRemoved);
|
||||
BuildControllers();
|
||||
Logger.DebugS("physics", $"Found {_controllers.Count} physics controllers.");
|
||||
|
||||
IoCManager.Resolve<IIslandManager>().Initialize();
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -121,8 +121,8 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
velocityConstraint.Friction = contact.Friction;
|
||||
velocityConstraint.Restitution = contact.Restitution;
|
||||
velocityConstraint.TangentSpeed = contact.TangentSpeed;
|
||||
velocityConstraint.IndexA = bodyA.IslandIndex;
|
||||
velocityConstraint.IndexB = bodyB.IslandIndex;
|
||||
velocityConstraint.IndexA = bodyA.IslandIndex[data.IslandIndex];
|
||||
velocityConstraint.IndexB = bodyB.IslandIndex[data.IslandIndex];
|
||||
|
||||
(velocityConstraint.InvMassA, velocityConstraint.InvMassB) = GetInvMass(bodyA, bodyB);
|
||||
velocityConstraint.InvIA = bodyA.InvI;
|
||||
@@ -137,8 +137,8 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
}
|
||||
|
||||
var positionConstraint = _positionConstraints[i];
|
||||
positionConstraint.IndexA = bodyA.IslandIndex;
|
||||
positionConstraint.IndexB = bodyB.IslandIndex;
|
||||
positionConstraint.IndexA = bodyA.IslandIndex[data.IslandIndex];
|
||||
positionConstraint.IndexB = bodyB.IslandIndex[data.IslandIndex];
|
||||
(positionConstraint.InvMassA, positionConstraint.InvMassB) = GetInvMass(bodyA, bodyB);
|
||||
// TODO: Dis
|
||||
// positionConstraint.LocalCenterA = bodyA._sweep.LocalCenter;
|
||||
|
||||
@@ -334,8 +334,8 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
|
||||
internal override void InitVelocityConstraints(SolverData data)
|
||||
{
|
||||
_indexA = BodyA.IslandIndex;
|
||||
_indexB = BodyB.IslandIndex;
|
||||
_indexA = BodyA.IslandIndex[data.IslandIndex];
|
||||
_indexB = BodyB.IslandIndex[data.IslandIndex];
|
||||
_localCenterA = Vector2.Zero; //BodyA->m_sweep.localCenter;
|
||||
_localCenterB = Vector2.Zero; //BodyB->m_sweep.localCenter;
|
||||
_invMassA = BodyA.InvMass;
|
||||
|
||||
@@ -159,8 +159,8 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
|
||||
internal override void InitVelocityConstraints(SolverData data)
|
||||
{
|
||||
_indexA = BodyA.IslandIndex;
|
||||
_indexB = BodyB.IslandIndex;
|
||||
_indexA = BodyA.IslandIndex[data.IslandIndex];
|
||||
_indexB = BodyB.IslandIndex[data.IslandIndex];
|
||||
_localCenterA = Vector2.Zero; // BodyA._sweep.LocalCenter;
|
||||
_localCenterB = Vector2.Zero; //BodyB._sweep.LocalCenter;
|
||||
_invMassA = BodyA.InvMass;
|
||||
|
||||
@@ -138,7 +138,7 @@ constraint structures. The body velocities/positions are held in compact, tempor
|
||||
arrays to increase the number of cache hits. Linear and angular velocity are
|
||||
stored in a single array since multiple arrays lead to multiple misses.
|
||||
*/
|
||||
internal sealed class PhysicsIsland
|
||||
public sealed class PhysicsIsland
|
||||
{
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
#if DEBUG
|
||||
@@ -148,6 +148,10 @@ stored in a single array since multiple arrays lead to multiple misses.
|
||||
|
||||
private ContactSolver _contactSolver = default!;
|
||||
|
||||
internal int ID { get; set; } = -1;
|
||||
|
||||
internal bool LoneIsland { get; set; }
|
||||
|
||||
private float _angTolSqr;
|
||||
private float _linTolSqr;
|
||||
private bool _warmStarting;
|
||||
@@ -169,8 +173,14 @@ stored in a single array since multiple arrays lead to multiple misses.
|
||||
private Vector2[] _positions = Array.Empty<Vector2>();
|
||||
private float[] _angles = Array.Empty<float>();
|
||||
|
||||
private bool _positionSolved = false;
|
||||
|
||||
internal SolverData SolverData = new();
|
||||
|
||||
private const int BodyIncrease = 8;
|
||||
private const int ContactIncrease = 4;
|
||||
private const int JointIncrease = 4;
|
||||
|
||||
/// <summary>
|
||||
/// How many bodies we can fit in the island before needing to re-size.
|
||||
/// </summary>
|
||||
@@ -250,9 +260,28 @@ stored in a single array since multiple arrays lead to multiple misses.
|
||||
_configManager.OnValueChanged(CVars.TimeToSleep, value => _timeToSleep = value);
|
||||
}
|
||||
|
||||
public void Append(List<IPhysBody> bodies, List<Contact> contacts, List<Joint> joints)
|
||||
{
|
||||
Resize(BodyCount + bodies.Count, ContactCount + contacts.Count, JointCount + joints.Count);
|
||||
foreach (var body in bodies)
|
||||
{
|
||||
Add(body);
|
||||
}
|
||||
|
||||
foreach (var contact in contacts)
|
||||
{
|
||||
Add(contact);
|
||||
}
|
||||
|
||||
foreach (var joint in joints)
|
||||
{
|
||||
Add(joint);
|
||||
}
|
||||
}
|
||||
|
||||
public void Add(IPhysBody body)
|
||||
{
|
||||
body.IslandIndex = BodyCount;
|
||||
body.IslandIndex[ID] = BodyCount;
|
||||
Bodies[BodyCount++] = body;
|
||||
}
|
||||
|
||||
@@ -268,6 +297,7 @@ stored in a single array since multiple arrays lead to multiple misses.
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
ID = -1;
|
||||
BodyCount = 0;
|
||||
ContactCount = 0;
|
||||
JointCount = 0;
|
||||
@@ -277,48 +307,39 @@ stored in a single array since multiple arrays lead to multiple misses.
|
||||
* Look there's a whole lot of stuff going on around here but all you need to know is it's trying to avoid
|
||||
* allocations where possible so it does a whole lot of passing data around and using arrays.
|
||||
*/
|
||||
|
||||
public void Reset(int bodyCapacity, int contactCapacity, int jointCapacity)
|
||||
public void Resize(int bodyCount, int contactCount, int jointCount)
|
||||
{
|
||||
BodyCapacity = bodyCapacity;
|
||||
BodyCount = 0;
|
||||
BodyCapacity = Math.Max(bodyCount, Bodies.Length);
|
||||
ContactCapacity = Math.Max(contactCount, _contacts.Length);
|
||||
JointCapacity = Math.Max(jointCount, _joints.Length);
|
||||
|
||||
ContactCapacity = contactCapacity;
|
||||
ContactCount = 0;
|
||||
|
||||
JointCapacity = jointCapacity;
|
||||
JointCount = 0;
|
||||
|
||||
if (Bodies.Length < bodyCapacity)
|
||||
if (Bodies.Length < BodyCapacity)
|
||||
{
|
||||
Array.Resize(ref Bodies, bodyCapacity);
|
||||
Array.Resize(ref _linearVelocities, bodyCapacity);
|
||||
Array.Resize(ref _angularVelocities, bodyCapacity);
|
||||
Array.Resize(ref _positions, bodyCapacity);
|
||||
Array.Resize(ref _angles, bodyCapacity);
|
||||
BodyCapacity = BodyIncrease * (int) MathF.Ceiling(BodyCapacity / (float) BodyIncrease);
|
||||
Array.Resize(ref Bodies, BodyCapacity);
|
||||
Array.Resize(ref _linearVelocities, BodyCapacity);
|
||||
Array.Resize(ref _angularVelocities, BodyCapacity);
|
||||
Array.Resize(ref _positions, BodyCapacity);
|
||||
Array.Resize(ref _angles, BodyCapacity);
|
||||
}
|
||||
|
||||
if (_contacts.Length < contactCapacity)
|
||||
if (_contacts.Length < ContactCapacity)
|
||||
{
|
||||
Array.Resize(ref _contacts, contactCapacity * 2);
|
||||
ContactCapacity = ContactIncrease * (int) MathF.Ceiling(ContactCapacity / (float) ContactIncrease);
|
||||
Array.Resize(ref _contacts, ContactCapacity * 2);
|
||||
}
|
||||
|
||||
if (_joints.Length < jointCapacity)
|
||||
if (_joints.Length < JointCapacity)
|
||||
{
|
||||
Array.Resize(ref _joints, jointCapacity * 2);
|
||||
JointCapacity = JointIncrease * (int) MathF.Ceiling(JointCapacity / (float) JointIncrease);
|
||||
Array.Resize(ref _joints, JointCapacity * 2);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Go through all the bodies in this island and solve.
|
||||
/// </summary>
|
||||
/// <param name="gravity"></param>
|
||||
/// <param name="frameTime"></param>
|
||||
/// <param name="dtRatio"></param>
|
||||
/// <param name="invDt"></param>
|
||||
/// <param name="prediction"></param>
|
||||
/// <param name="deferredUpdates">Add any transform updates to a deferred list</param>
|
||||
public void Solve(Vector2 gravity, float frameTime, float dtRatio, float invDt, bool prediction, List<(ITransformComponent, IPhysBody)> deferredUpdates)
|
||||
public void Solve(Vector2 gravity, float frameTime, float dtRatio, float invDt, bool prediction)
|
||||
{
|
||||
#if DEBUG
|
||||
_debugBodies.Clear();
|
||||
@@ -366,6 +387,7 @@ stored in a single array since multiple arrays lead to multiple misses.
|
||||
SolverData.FrameTime = frameTime;
|
||||
SolverData.DtRatio = dtRatio;
|
||||
SolverData.InvDt = invDt;
|
||||
SolverData.IslandIndex = ID;
|
||||
|
||||
SolverData.LinearVelocities = _linearVelocities;
|
||||
SolverData.AngularVelocities = _angularVelocities;
|
||||
@@ -443,7 +465,7 @@ stored in a single array since multiple arrays lead to multiple misses.
|
||||
_angles[i] = angle;
|
||||
}
|
||||
|
||||
var positionSolved = false;
|
||||
_positionSolved = false;
|
||||
|
||||
for (var i = 0; i < _positionIterations; i++)
|
||||
{
|
||||
@@ -464,11 +486,14 @@ stored in a single array since multiple arrays lead to multiple misses.
|
||||
|
||||
if (contactsOkay && jointsOkay)
|
||||
{
|
||||
positionSolved = true;
|
||||
_positionSolved = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal void UpdateBodies(List<(ITransformComponent, IPhysBody)> deferredUpdates)
|
||||
{
|
||||
// Update data on bodies by copying the buffers back
|
||||
for (var i = 0; i < BodyCount; i++)
|
||||
{
|
||||
@@ -517,39 +542,72 @@ stored in a single array since multiple arrays lead to multiple misses.
|
||||
body.AngularVelocity = _angularVelocities[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sleep bodies if needed. Prediction won't accumulate sleep-time for bodies.
|
||||
if (!prediction && _sleepAllowed)
|
||||
internal void SleepBodies(bool prediction, float frameTime)
|
||||
{
|
||||
if (LoneIsland)
|
||||
{
|
||||
var minSleepTime = float.MaxValue;
|
||||
|
||||
for (var i = 0; i < BodyCount; i++)
|
||||
{
|
||||
var body = Bodies[i];
|
||||
|
||||
if (body.BodyType == BodyType.Static)
|
||||
continue;
|
||||
|
||||
if (!body.SleepingAllowed ||
|
||||
body.AngularVelocity * body.AngularVelocity > _angTolSqr ||
|
||||
Vector2.Dot(body.LinearVelocity, body.LinearVelocity) > _linTolSqr)
|
||||
{
|
||||
body.SleepTime = 0.0f;
|
||||
minSleepTime = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
body.SleepTime += frameTime;
|
||||
minSleepTime = MathF.Min(minSleepTime, body.SleepTime);
|
||||
}
|
||||
}
|
||||
|
||||
if (minSleepTime >= _timeToSleep && positionSolved)
|
||||
if (!prediction && _sleepAllowed)
|
||||
{
|
||||
for (var i = 0; i < BodyCount; i++)
|
||||
{
|
||||
var body = Bodies[i];
|
||||
body.Awake = false;
|
||||
|
||||
if (body.BodyType == BodyType.Static) continue;
|
||||
|
||||
if (!body.SleepingAllowed ||
|
||||
body.AngularVelocity * body.AngularVelocity > _angTolSqr ||
|
||||
Vector2.Dot(body.LinearVelocity, body.LinearVelocity) > _linTolSqr)
|
||||
{
|
||||
body.SleepTime = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
body.SleepTime += frameTime;
|
||||
}
|
||||
|
||||
if (body.SleepTime >= _timeToSleep && _positionSolved)
|
||||
{
|
||||
body.Awake = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Sleep bodies if needed. Prediction won't accumulate sleep-time for bodies.
|
||||
if (!prediction && _sleepAllowed)
|
||||
{
|
||||
var minSleepTime = float.MaxValue;
|
||||
|
||||
for (var i = 0; i < BodyCount; i++)
|
||||
{
|
||||
var body = Bodies[i];
|
||||
|
||||
if (body.BodyType == BodyType.Static) continue;
|
||||
|
||||
if (!body.SleepingAllowed ||
|
||||
body.AngularVelocity * body.AngularVelocity > _angTolSqr ||
|
||||
Vector2.Dot(body.LinearVelocity, body.LinearVelocity) > _linTolSqr)
|
||||
{
|
||||
body.SleepTime = 0.0f;
|
||||
minSleepTime = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
body.SleepTime += frameTime;
|
||||
minSleepTime = MathF.Min(minSleepTime, body.SleepTime);
|
||||
}
|
||||
}
|
||||
|
||||
if (minSleepTime >= _timeToSleep && _positionSolved)
|
||||
{
|
||||
for (var i = 0; i < BodyCount; i++)
|
||||
{
|
||||
var body = Bodies[i];
|
||||
body.Awake = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -561,6 +619,8 @@ stored in a single array since multiple arrays lead to multiple misses.
|
||||
/// </summary>
|
||||
internal sealed class SolverData
|
||||
{
|
||||
public int IslandIndex { get; set; } = -1;
|
||||
|
||||
public float FrameTime { get; set; }
|
||||
public float DtRatio { get; set; }
|
||||
public float InvDt { get; set; }
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
[Dependency] private readonly IConfigurationManager _configManager = default!;
|
||||
[Dependency] private readonly IEntityManager _entityManager = default!;
|
||||
[Dependency] private readonly IMapManager _mapManager = default!;
|
||||
[Dependency] private readonly IIslandManager _islandManager = default!;
|
||||
|
||||
private SharedPhysicsSystem _physicsSystem = default!;
|
||||
|
||||
@@ -104,7 +105,9 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
|
||||
private Queue<CollisionChangeMessage> _queuedCollisionMessages = new();
|
||||
|
||||
private PhysicsIsland _island = default!;
|
||||
private List<IPhysBody> _islandBodies = new(64);
|
||||
private List<Contact> _islandContacts = new(32);
|
||||
private List<Joint> _islandJoints = new(8);
|
||||
|
||||
/// <summary>
|
||||
/// To build islands we do a depth-first search of all colliding bodies and group them together.
|
||||
@@ -130,8 +133,6 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
IoCManager.InjectDependencies(this);
|
||||
ContactManager.Initialize();
|
||||
ContactManager.MapId = MapId;
|
||||
_island = new PhysicsIsland();
|
||||
_island.Initialize();
|
||||
|
||||
_autoClearForces = _configManager.GetCVar(CVars.AutoClearForces);
|
||||
_configManager.OnValueChanged(CVars.AutoClearForces, value => _autoClearForces = value);
|
||||
@@ -447,8 +448,7 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
|
||||
private void Solve(float frameTime, float dtRatio, float invDt, bool prediction)
|
||||
{
|
||||
// Re-size island for worst-case -> TODO Probably smaller than this given everything's awake at the start?
|
||||
_island.Reset(Bodies.Count, ContactManager.ContactCount, Joints.Count);
|
||||
_islandManager.InitializePools();
|
||||
|
||||
DebugTools.Assert(_islandSet.Count == 0);
|
||||
|
||||
@@ -463,7 +463,7 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
}
|
||||
|
||||
// Build and simulated islands from awake bodies.
|
||||
// Ideally you don't need a stack size for all bodies but we'll optimise it later.
|
||||
// Ideally you don't need a stack size for all bodies but we'll TODO: optimise it later.
|
||||
var stackSize = Bodies.Count;
|
||||
if (stackSize > _stack.Length)
|
||||
{
|
||||
@@ -486,17 +486,19 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
seed.BodyType == BodyType.Static) continue;
|
||||
|
||||
// Start of a new island
|
||||
_island.Clear();
|
||||
_islandBodies.Clear();
|
||||
_islandContacts.Clear();
|
||||
_islandJoints.Clear();
|
||||
var stackCount = 0;
|
||||
_stack[stackCount++] = seed;
|
||||
|
||||
_islandSet.Add(seed);
|
||||
// TODO: Probably don't need _islandSet anymore.
|
||||
seed.Island = true;
|
||||
|
||||
while (stackCount > 0)
|
||||
{
|
||||
var body = _stack[--stackCount];
|
||||
_island.Add(body);
|
||||
_islandBodies.Add(body);
|
||||
_islandSet.Add(body);
|
||||
|
||||
// Static bodies don't propagate islands
|
||||
@@ -518,7 +520,7 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
// Skip sensors.
|
||||
if (contact.FixtureA?.Hard != true || contact.FixtureB?.Hard != true) continue;
|
||||
|
||||
_island.Add(contact);
|
||||
_islandContacts.Add(contact);
|
||||
contact.IslandFlag = true;
|
||||
|
||||
var other = contactEdge.Other!;
|
||||
@@ -529,9 +531,6 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
DebugTools.Assert(stackCount < stackSize);
|
||||
_stack[stackCount++] = other;
|
||||
|
||||
if (!_islandSet.Contains(body))
|
||||
_islandSet.Add(body);
|
||||
|
||||
other.Island = true;
|
||||
}
|
||||
|
||||
@@ -547,7 +546,7 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
// Don't simulate joints connected to inactive bodies.
|
||||
if (!other.CanCollide) continue;
|
||||
|
||||
_island.Add(je.Joint);
|
||||
_islandJoints.Add(je.Joint);
|
||||
je.Joint.IslandFlag = true;
|
||||
|
||||
if (other.Island) continue;
|
||||
@@ -555,19 +554,18 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
DebugTools.Assert(stackCount < stackSize);
|
||||
_stack[stackCount++] = other;
|
||||
|
||||
if (!_islandSet.Contains(body))
|
||||
_islandSet.Add(body);
|
||||
|
||||
other.Island = true;
|
||||
}
|
||||
}
|
||||
|
||||
_island.Solve(Gravity, frameTime, dtRatio, invDt, prediction, _deferredUpdates);
|
||||
_islandManager
|
||||
.AllocateIsland(_islandBodies.Count, _islandContacts.Count, _islandJoints.Count)
|
||||
.Append(_islandBodies, _islandContacts, _islandJoints);
|
||||
|
||||
// Post-solve cleanup for island
|
||||
for (var i = 0; i < _island.BodyCount; i++)
|
||||
// Allow static bodies to be re-used in other islands
|
||||
for (var i = 0; i < _islandBodies.Count; i++)
|
||||
{
|
||||
var body = _island.Bodies[i];
|
||||
var body = _islandBodies[i];
|
||||
|
||||
// Static bodies can participate in other islands
|
||||
if (body.BodyType == BodyType.Static)
|
||||
@@ -577,6 +575,8 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
}
|
||||
}
|
||||
|
||||
SolveIslands(frameTime, dtRatio, invDt, prediction);
|
||||
|
||||
foreach (var body in _islandSet)
|
||||
{
|
||||
if (!body.Island || body.Deleted)
|
||||
@@ -584,6 +584,7 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
continue;
|
||||
}
|
||||
|
||||
body.IslandIndex.Clear();
|
||||
body.Island = false;
|
||||
DebugTools.Assert(body.BodyType != BodyType.Static);
|
||||
|
||||
@@ -596,6 +597,32 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
ContactManager.PostSolve();
|
||||
}
|
||||
|
||||
private void SolveIslands(float frameTime, float dtRatio, float invDt, bool prediction)
|
||||
{
|
||||
var islands = _islandManager.GetActive;
|
||||
// Islands are already pre-sorted
|
||||
var iBegin = 0;
|
||||
|
||||
while (iBegin < islands.Count)
|
||||
{
|
||||
var island = islands[iBegin];
|
||||
|
||||
island.Solve(Gravity, frameTime, dtRatio, invDt, prediction);
|
||||
iBegin++;
|
||||
// TODO: Submit rest in parallel if applicable
|
||||
}
|
||||
|
||||
// TODO: parallel dispatch here
|
||||
|
||||
// Update bodies sequentially to avoid race conditions. May be able to do this parallel someday
|
||||
// but easier to just do this for now.
|
||||
foreach (var island in islands)
|
||||
{
|
||||
island.UpdateBodies(_deferredUpdates);
|
||||
island.SleepBodies(prediction, frameTime);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearForces()
|
||||
{
|
||||
foreach (var body in AwakeBodies)
|
||||
|
||||
@@ -15,7 +15,7 @@ namespace Robust.Shared.Physics
|
||||
{
|
||||
bool IgnoreGravity { get; set; }
|
||||
|
||||
int IslandIndex { get; set; }
|
||||
Dictionary<int, int> IslandIndex { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Has this body already been added to a physics island
|
||||
|
||||
190
Robust.Shared/Physics/IslandManager.cs
Normal file
190
Robust.Shared/Physics/IslandManager.cs
Normal file
@@ -0,0 +1,190 @@
|
||||
/*
|
||||
Bullet Continuous Collision Detection and Physics Library
|
||||
Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/
|
||||
This software is provided 'as-is', without any express or implied warranty.
|
||||
In no event will the authors be held liable for any damages arising from the use of this software.
|
||||
Permission is granted to anyone to use this software for any purpose,
|
||||
including commercial applications, and to alter it and redistribute it freely,
|
||||
subject to the following restrictions:
|
||||
1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software. If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
|
||||
2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
|
||||
3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Physics
|
||||
{
|
||||
internal interface IIslandManager
|
||||
{
|
||||
void Initialize();
|
||||
void InitializePools();
|
||||
PhysicsIsland AllocateIsland(int numBodies, int numContacts, int numJoints);
|
||||
|
||||
IReadOnlyList<PhysicsIsland> GetActive { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Contains physics islands for use by PhysicsMaps.
|
||||
/// Key difference from bullet3 is we won't merge small islands together due to the way box2d sleeping works.
|
||||
/// </summary>
|
||||
internal sealed class IslandManager : IIslandManager
|
||||
{
|
||||
private static readonly IslandBodyCapacitySort CapacitySort = new();
|
||||
private static readonly IslandBodyCountSort CountSort = new();
|
||||
|
||||
/// <summary>
|
||||
/// The island all non-contact non-joint bodies are added to to batch together. This needs its own custom sleeping
|
||||
/// given we cant wait for every body to be ready to sleep.
|
||||
/// </summary>
|
||||
private PhysicsIsland _loneIsland = new() {LoneIsland = true, ID = 0};
|
||||
|
||||
/// <summary>
|
||||
/// Contains islands currently in use.
|
||||
/// </summary>
|
||||
private List<PhysicsIsland> _activeIslands = new();
|
||||
|
||||
private List<PhysicsIsland> _freeIslands = new();
|
||||
|
||||
/// <summary>
|
||||
/// Contains every single PhysicsIsland.
|
||||
/// </summary>
|
||||
private List<PhysicsIsland> _allocatedIslands = new();
|
||||
|
||||
public IReadOnlyList<PhysicsIsland> GetActive
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_loneIsland.BodyCount > 0)
|
||||
{
|
||||
_activeIslands.Add(_loneIsland);
|
||||
}
|
||||
|
||||
// Look this is kinda stinky but it's only called at the appropriate place for now
|
||||
_activeIslands.Sort(CountSort);
|
||||
return _activeIslands;
|
||||
}
|
||||
}
|
||||
|
||||
public void Initialize()
|
||||
{
|
||||
_loneIsland.Initialize();
|
||||
// Set an initial size so we don't spam a bunch of array resizes at the start
|
||||
_loneIsland.Resize(64, 32, 8);
|
||||
}
|
||||
|
||||
public void InitializePools()
|
||||
{
|
||||
_loneIsland.Clear();
|
||||
_activeIslands.Clear();
|
||||
_freeIslands.Clear();
|
||||
|
||||
// Check whether allocated islands are sorted
|
||||
var lastCapacity = 0;
|
||||
var isSorted = true;
|
||||
|
||||
foreach (var island in _allocatedIslands)
|
||||
{
|
||||
var capacity = island.BodyCapacity;
|
||||
if (capacity > lastCapacity)
|
||||
{
|
||||
isSorted = false;
|
||||
break;
|
||||
}
|
||||
|
||||
lastCapacity = capacity;
|
||||
}
|
||||
|
||||
if (!isSorted)
|
||||
{
|
||||
_allocatedIslands.Sort(CapacitySort);
|
||||
}
|
||||
|
||||
// TODO: Look at removing islands occasionally just to avoid memory bloat over time.
|
||||
// e.g. every 30 seconds go through every island that hasn't been used in 30 seconds and remove it.
|
||||
|
||||
// Free up islands
|
||||
foreach (var island in _allocatedIslands)
|
||||
{
|
||||
island.Clear();
|
||||
_freeIslands.Add(island);
|
||||
}
|
||||
}
|
||||
|
||||
public PhysicsIsland AllocateIsland(int bodyCount, int contactCount, int jointCount)
|
||||
{
|
||||
// If only 1 body then that means no contacts or joints. This also means that we can add it to the loneisland
|
||||
if (bodyCount == 1)
|
||||
{
|
||||
// Island manages re-sizes internally so don't need to worry about this being hammered.
|
||||
_loneIsland.Resize(_loneIsland.BodyCount + 1, 0, 0);
|
||||
return _loneIsland;
|
||||
}
|
||||
|
||||
PhysicsIsland? island = null;
|
||||
|
||||
// Because bullet3 combines islands it's more relevant for them to allocate by bodies but at least for now
|
||||
// we don't combine.
|
||||
if (_freeIslands.Count > 0)
|
||||
{
|
||||
// Try to use an existing island; with the smallest size that can hold us if possible.
|
||||
var iFound = _freeIslands.Count;
|
||||
|
||||
for (var i = _freeIslands.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (_freeIslands[i].BodyCapacity >= bodyCount)
|
||||
{
|
||||
iFound = i;
|
||||
island = _freeIslands[i];
|
||||
DebugTools.Assert(island.BodyCount == 0 && island.ContactCount == 0 && island.JointCount == 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (island != null)
|
||||
{
|
||||
var iDest = iFound;
|
||||
var iSrc = iDest + 1;
|
||||
while (iSrc < _freeIslands.Count)
|
||||
{
|
||||
_freeIslands[iDest++] = _freeIslands[iSrc++];
|
||||
}
|
||||
|
||||
_freeIslands.RemoveAt(_freeIslands.Count - 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (island == null)
|
||||
{
|
||||
island = new PhysicsIsland();
|
||||
island.Initialize();
|
||||
_allocatedIslands.Add(island);
|
||||
}
|
||||
|
||||
island.Resize(bodyCount, contactCount, jointCount);
|
||||
// 0 ID taken up by LoneIsland
|
||||
island.ID = _activeIslands.Count + 1;
|
||||
_activeIslands.Add(island);
|
||||
return island;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class IslandBodyCapacitySort : Comparer<PhysicsIsland>
|
||||
{
|
||||
public override int Compare(PhysicsIsland? x, PhysicsIsland? y)
|
||||
{
|
||||
if (x == null || y == null) return 0;
|
||||
return x.BodyCapacity > y.BodyCapacity ? 1 : 0;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class IslandBodyCountSort : Comparer<PhysicsIsland>
|
||||
{
|
||||
public override int Compare(PhysicsIsland? x, PhysicsIsland? y)
|
||||
{
|
||||
if (x == null || y == null) return 0;
|
||||
return x.BodyCount > y.BodyCount ? 1 : 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -46,6 +46,7 @@ namespace Robust.Shared
|
||||
IoCManager.Register<IComponentDependencyManager, ComponentDependencyManager>();
|
||||
IoCManager.Register<ISandboxHelper, SandboxHelper>();
|
||||
IoCManager.Register<ICollisionManager, CollisionManager>();
|
||||
IoCManager.Register<IIslandManager, IslandManager>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user