mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-02-15 11:40:52 +01:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
75fc9089c3 | ||
|
|
0972601a43 | ||
|
|
603c252c48 | ||
|
|
d5b1c044b7 | ||
|
|
4600f0531d | ||
|
|
c88498eca9 | ||
|
|
f15f1eb345 | ||
|
|
5be3ced05a | ||
|
|
7f03e88e97 | ||
|
|
8e3fa3e52d | ||
|
|
f9ae3e1fc2 | ||
|
|
bf9e95fa8a | ||
|
|
030a7d265b | ||
|
|
df70e94743 | ||
|
|
d68cd4d7eb | ||
|
|
d098881bff | ||
|
|
b8fbe32c27 | ||
|
|
02d2bd31e7 | ||
|
|
bd0dba0df0 |
Submodule Lidgren.Network/Lidgren.Network updated: dd285c9246...b5483c7a7e
@@ -10,7 +10,7 @@ Use the [content repo](https://github.com/space-wizards/space-station-14) for ac
|
||||
|
||||
## Documentation/Wiki
|
||||
|
||||
The [HackMD Wiki](https://hackmd.io/@ss14/docs/wiki) has documentation on SS14s content, engine, game design and more. We also have lots of resources for new contributors to the project.
|
||||
The [wiki](https://docs.spacestation14.io/) has documentation on SS14s content, engine, game design and more. We also have lots of resources for new contributors to the project.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
@@ -533,8 +533,7 @@ namespace Robust.Client.WebView.Cef
|
||||
if (_control.Owner.Disposed)
|
||||
return false;
|
||||
|
||||
// TODO CEF: Get actual scale factor?
|
||||
screenInfo.DeviceScaleFactor = 1.0f;
|
||||
screenInfo.DeviceScaleFactor = _control.Owner.UIScale;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<Import Project="..\MSBuild\Robust.Engine.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<PlatformTarget>x64</PlatformTarget>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<OutputType>WinExe</OutputType>
|
||||
|
||||
@@ -4,177 +4,186 @@ using Robust.Shared;
|
||||
using Robust.Shared.Utility;
|
||||
using C = System.Console;
|
||||
|
||||
namespace Robust.Client
|
||||
namespace Robust.Client;
|
||||
|
||||
internal sealed class CommandLineArgs
|
||||
{
|
||||
internal sealed class CommandLineArgs
|
||||
public MountOptions MountOptions { get; }
|
||||
public bool Headless { get; }
|
||||
public bool SelfContained { get; }
|
||||
public bool Connect { get; }
|
||||
public string ConnectAddress { get; }
|
||||
public string? Ss14Address { get; }
|
||||
public bool Launcher { get; }
|
||||
public string? Username { get; }
|
||||
public IReadOnlyCollection<(string key, string value)> CVars { get; }
|
||||
public IReadOnlyCollection<(string key, string value)> LogLevels { get; }
|
||||
public IReadOnlyList<string> ExecCommands { get; set; }
|
||||
|
||||
// Manual parser because C# has no good command line parsing libraries. Also dependencies bad.
|
||||
// Also I don't like spending 100ms parsing command line args. Do you?
|
||||
public static bool TryParse(IReadOnlyList<string> args, [NotNullWhen(true)] out CommandLineArgs? parsed)
|
||||
{
|
||||
public MountOptions MountOptions { get; }
|
||||
public bool Headless { get; }
|
||||
public bool SelfContained { get; }
|
||||
public bool Connect { get; }
|
||||
public string ConnectAddress { get; }
|
||||
public string? Ss14Address { get; }
|
||||
public bool Launcher { get; }
|
||||
public string? Username { get; }
|
||||
public IReadOnlyCollection<(string key, string value)> CVars { get; }
|
||||
public IReadOnlyCollection<(string key, string value)> LogLevels { get; }
|
||||
parsed = null;
|
||||
var headless = false;
|
||||
var selfContained = false;
|
||||
var connect = false;
|
||||
var connectAddress = "localhost";
|
||||
string? ss14Address = null;
|
||||
var launcher = false;
|
||||
string? username = null;
|
||||
var cvars = new List<(string, string)>();
|
||||
var logLevels = new List<(string, string)>();
|
||||
var mountOptions = new MountOptions();
|
||||
var execCommands = new List<string>();
|
||||
|
||||
// Manual parser because C# has no good command line parsing libraries. Also dependencies bad.
|
||||
// Also I don't like spending 100ms parsing command line args. Do you?
|
||||
public static bool TryParse(IReadOnlyList<string> args, [NotNullWhen(true)] out CommandLineArgs? parsed)
|
||||
using var enumerator = args.GetEnumerator();
|
||||
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
parsed = null;
|
||||
var headless = false;
|
||||
var selfContained = false;
|
||||
var connect = false;
|
||||
var connectAddress = "localhost";
|
||||
string? ss14Address = null;
|
||||
var launcher = false;
|
||||
string? username = null;
|
||||
var cvars = new List<(string, string)>();
|
||||
var logLevels = new List<(string, string)>();
|
||||
var mountOptions = new MountOptions();
|
||||
|
||||
using var enumerator = args.GetEnumerator();
|
||||
|
||||
while (enumerator.MoveNext())
|
||||
var arg = enumerator.Current;
|
||||
if (arg == "--connect")
|
||||
{
|
||||
var arg = enumerator.Current;
|
||||
if (arg == "--connect")
|
||||
connect = true;
|
||||
}
|
||||
else if (arg == "--connect-address")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
connect = true;
|
||||
}
|
||||
else if (arg == "--connect-address")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing connection address.");
|
||||
return false;
|
||||
}
|
||||
|
||||
connectAddress = enumerator.Current;
|
||||
}
|
||||
else if (arg == "--ss14-address")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing SS14 address.");
|
||||
return false;
|
||||
}
|
||||
|
||||
ss14Address = enumerator.Current;
|
||||
}
|
||||
else if (arg == "--self-contained")
|
||||
{
|
||||
selfContained = true;
|
||||
}
|
||||
else if (arg == "--launcher")
|
||||
{
|
||||
launcher = true;
|
||||
}
|
||||
else if (arg == "--headless")
|
||||
{
|
||||
headless = true;
|
||||
}
|
||||
else if (arg == "--username")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing username.");
|
||||
return false;
|
||||
}
|
||||
|
||||
username = enumerator.Current;
|
||||
}
|
||||
else if (arg == "--cvar")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing cvar value.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var cvar = enumerator.Current;
|
||||
DebugTools.AssertNotNull(cvar);
|
||||
var pos = cvar.IndexOf('=');
|
||||
|
||||
if (pos == -1)
|
||||
{
|
||||
C.WriteLine("Expected = in cvar.");
|
||||
return false;
|
||||
}
|
||||
|
||||
cvars.Add((cvar[..pos], cvar[(pos + 1)..]));
|
||||
}
|
||||
else if (arg == "--mount-zip")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing mount path");
|
||||
return false;
|
||||
}
|
||||
|
||||
mountOptions.ZipMounts.Add(enumerator.Current);
|
||||
}
|
||||
else if (arg == "--mount-dir")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing mount path");
|
||||
return false;
|
||||
}
|
||||
|
||||
mountOptions.DirMounts.Add(enumerator.Current);
|
||||
}
|
||||
else if (arg == "--loglevel")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing loglevel sawmill.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var loglevel = enumerator.Current;
|
||||
DebugTools.AssertNotNull(loglevel);
|
||||
var pos = loglevel.IndexOf('=');
|
||||
|
||||
if (pos == -1)
|
||||
{
|
||||
C.WriteLine("Expected = in loglevel.");
|
||||
return false;
|
||||
}
|
||||
|
||||
logLevels.Add((loglevel[..pos], loglevel[(pos + 1)..]));
|
||||
}
|
||||
else if (arg == "--help")
|
||||
{
|
||||
PrintHelp();
|
||||
C.WriteLine("Missing connection address.");
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
C.WriteLine("Unknown argument: {0}", arg);
|
||||
}
|
||||
|
||||
connectAddress = enumerator.Current;
|
||||
}
|
||||
else if (arg == "--ss14-address")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing SS14 address.");
|
||||
return false;
|
||||
}
|
||||
|
||||
parsed = new CommandLineArgs(
|
||||
headless,
|
||||
selfContained,
|
||||
connect,
|
||||
launcher,
|
||||
username,
|
||||
cvars,
|
||||
logLevels,
|
||||
connectAddress,
|
||||
ss14Address,
|
||||
mountOptions);
|
||||
ss14Address = enumerator.Current;
|
||||
}
|
||||
else if (arg == "--self-contained")
|
||||
{
|
||||
selfContained = true;
|
||||
}
|
||||
else if (arg == "--launcher")
|
||||
{
|
||||
launcher = true;
|
||||
}
|
||||
else if (arg == "--headless")
|
||||
{
|
||||
headless = true;
|
||||
}
|
||||
else if (arg == "--username")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing username.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
username = enumerator.Current;
|
||||
}
|
||||
else if (arg == "--cvar")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing cvar value.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var cvar = enumerator.Current;
|
||||
DebugTools.AssertNotNull(cvar);
|
||||
var pos = cvar.IndexOf('=');
|
||||
|
||||
if (pos == -1)
|
||||
{
|
||||
C.WriteLine("Expected = in cvar.");
|
||||
return false;
|
||||
}
|
||||
|
||||
cvars.Add((cvar[..pos], cvar[(pos + 1)..]));
|
||||
}
|
||||
else if (arg == "--mount-zip")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing mount path");
|
||||
return false;
|
||||
}
|
||||
|
||||
mountOptions.ZipMounts.Add(enumerator.Current);
|
||||
}
|
||||
else if (arg == "--mount-dir")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing mount path");
|
||||
return false;
|
||||
}
|
||||
|
||||
mountOptions.DirMounts.Add(enumerator.Current);
|
||||
}
|
||||
else if (arg == "--loglevel")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing loglevel sawmill.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var loglevel = enumerator.Current;
|
||||
DebugTools.AssertNotNull(loglevel);
|
||||
var pos = loglevel.IndexOf('=');
|
||||
|
||||
if (pos == -1)
|
||||
{
|
||||
C.WriteLine("Expected = in loglevel.");
|
||||
return false;
|
||||
}
|
||||
|
||||
logLevels.Add((loglevel[..pos], loglevel[(pos + 1)..]));
|
||||
}
|
||||
else if (arg == "--help")
|
||||
{
|
||||
PrintHelp();
|
||||
return false;
|
||||
}
|
||||
else if (arg.StartsWith("+"))
|
||||
{
|
||||
execCommands.Add(arg[1..]);
|
||||
}
|
||||
else
|
||||
{
|
||||
C.WriteLine("Unknown argument: {0}", arg);
|
||||
}
|
||||
}
|
||||
|
||||
private static void PrintHelp()
|
||||
{
|
||||
C.WriteLine(@"
|
||||
parsed = new CommandLineArgs(
|
||||
headless,
|
||||
selfContained,
|
||||
connect,
|
||||
launcher,
|
||||
username,
|
||||
cvars,
|
||||
logLevels,
|
||||
connectAddress,
|
||||
ss14Address,
|
||||
mountOptions,
|
||||
execCommands);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void PrintHelp()
|
||||
{
|
||||
C.WriteLine(@"
|
||||
Usage: Robust.Client [options] [+command [+command]]
|
||||
|
||||
Options:
|
||||
--headless Run without graphics/audio/input.
|
||||
--self-contained Store data relative to executable instead of user-global locations.
|
||||
@@ -189,30 +198,34 @@ Options:
|
||||
--mount-dir Resource directory to mount.
|
||||
--mount-zip Resource zip to mount.
|
||||
--help Display this help text and exit.
|
||||
");
|
||||
}
|
||||
|
||||
private CommandLineArgs(
|
||||
bool headless,
|
||||
bool selfContained,
|
||||
bool connect,
|
||||
bool launcher,
|
||||
string? username,
|
||||
IReadOnlyCollection<(string key, string value)> cVars,
|
||||
IReadOnlyCollection<(string key, string value)> logLevels,
|
||||
string connectAddress, string? ss14Address,
|
||||
MountOptions mountOptions)
|
||||
{
|
||||
Headless = headless;
|
||||
SelfContained = selfContained;
|
||||
Connect = connect;
|
||||
Launcher = launcher;
|
||||
Username = username;
|
||||
CVars = cVars;
|
||||
LogLevels = logLevels;
|
||||
ConnectAddress = connectAddress;
|
||||
Ss14Address = ss14Address;
|
||||
MountOptions = mountOptions;
|
||||
}
|
||||
+command: You can pass a set of commands, prefixed by +,
|
||||
to be executed in the console in order after the game has finished initializing.
|
||||
");
|
||||
}
|
||||
|
||||
private CommandLineArgs(
|
||||
bool headless,
|
||||
bool selfContained,
|
||||
bool connect,
|
||||
bool launcher,
|
||||
string? username,
|
||||
IReadOnlyCollection<(string key, string value)> cVars,
|
||||
IReadOnlyCollection<(string key, string value)> logLevels,
|
||||
string connectAddress, string? ss14Address,
|
||||
MountOptions mountOptions,
|
||||
IReadOnlyList<string> execCommands)
|
||||
{
|
||||
Headless = headless;
|
||||
SelfContained = selfContained;
|
||||
Connect = connect;
|
||||
Launcher = launcher;
|
||||
Username = username;
|
||||
CVars = cVars;
|
||||
LogLevels = logLevels;
|
||||
ConnectAddress = connectAddress;
|
||||
Ss14Address = ss14Address;
|
||||
MountOptions = mountOptions;
|
||||
ExecCommands = execCommands;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,6 +24,9 @@ namespace Robust.Client.Console.Commands
|
||||
case "aabbs":
|
||||
system.Flags ^= PhysicsDebugFlags.AABBs;
|
||||
break;
|
||||
case "com":
|
||||
system.Flags ^= PhysicsDebugFlags.COM;
|
||||
break;
|
||||
case "contactnormals":
|
||||
system.Flags ^= PhysicsDebugFlags.ContactNormals;
|
||||
break;
|
||||
|
||||
@@ -162,6 +162,7 @@ namespace Robust.Client.Debugging
|
||||
ShapeInfo = 1 << 3,
|
||||
Joints = 1 << 4,
|
||||
AABBs = 1 << 5,
|
||||
COM = 1 << 6,
|
||||
}
|
||||
|
||||
internal sealed class PhysicsDebugOverlay : Overlay
|
||||
@@ -190,13 +191,13 @@ namespace Robust.Client.Debugging
|
||||
_font = new VectorFont(cache.GetResource<FontResource>("/Fonts/NotoSans/NotoSans-Regular.ttf"), 10);
|
||||
}
|
||||
|
||||
private void DrawWorld(DrawingHandleWorld worldHandle)
|
||||
private void DrawWorld(DrawingHandleWorld worldHandle, OverlayDrawArgs args)
|
||||
{
|
||||
var viewport = _eyeManager.GetWorldViewport();
|
||||
var viewBounds = _eyeManager.GetWorldViewbounds();
|
||||
var viewBounds = args.WorldBounds;
|
||||
var viewAABB = args.WorldAABB;
|
||||
var mapId = _eyeManager.CurrentMap;
|
||||
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.Shapes) != 0 && !viewport.IsEmpty())
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.Shapes) != 0)
|
||||
{
|
||||
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, viewBounds))
|
||||
{
|
||||
@@ -237,7 +238,32 @@ namespace Robust.Client.Debugging
|
||||
}
|
||||
}
|
||||
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.AABBs) != 0 && !viewport.IsEmpty())
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.COM) != 0)
|
||||
{
|
||||
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, viewBounds))
|
||||
{
|
||||
Color color;
|
||||
const float Alpha = 0.25f;
|
||||
float size;
|
||||
|
||||
if (physBody.Owner.HasComponent<MapGridComponent>())
|
||||
{
|
||||
color = Color.Orange.WithAlpha(Alpha);
|
||||
size = 1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
color = Color.Purple.WithAlpha(Alpha);
|
||||
size = 0.2f;
|
||||
}
|
||||
|
||||
var transform = physBody.GetTransform();
|
||||
|
||||
worldHandle.DrawCircle(Transform.Mul(transform, physBody.LocalCenter), size, color);
|
||||
}
|
||||
}
|
||||
|
||||
if ((_debugPhysicsSystem.Flags & PhysicsDebugFlags.AABBs) != 0)
|
||||
{
|
||||
foreach (var physBody in _physicsSystem.GetCollidingEntities(mapId, viewBounds))
|
||||
{
|
||||
@@ -271,7 +297,7 @@ namespace Robust.Client.Debugging
|
||||
{
|
||||
if (jointComponent.JointCount == 0 ||
|
||||
!_entityManager.TryGetComponent(jointComponent.Owner.Uid, out TransformComponent? xf1) ||
|
||||
!viewport.Contains(xf1.WorldPosition)) continue;
|
||||
!viewAABB.Contains(xf1.WorldPosition)) continue;
|
||||
|
||||
foreach (var (_, joint) in jointComponent.Joints)
|
||||
{
|
||||
@@ -312,7 +338,7 @@ namespace Robust.Client.Debugging
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawScreen(DrawingHandleScreen screenHandle)
|
||||
private void DrawScreen(DrawingHandleScreen screenHandle, OverlayDrawArgs args)
|
||||
{
|
||||
var mapId = _eyeManager.CurrentMap;
|
||||
var mousePos = _inputManager.MouseScreenPosition;
|
||||
@@ -359,10 +385,10 @@ namespace Robust.Client.Debugging
|
||||
switch (args.Space)
|
||||
{
|
||||
case OverlaySpace.ScreenSpace:
|
||||
DrawScreen((DrawingHandleScreen) args.DrawingHandle);
|
||||
DrawScreen((DrawingHandleScreen) args.DrawingHandle, args);
|
||||
break;
|
||||
case OverlaySpace.WorldSpace:
|
||||
DrawWorld((DrawingHandleWorld) args.DrawingHandle);
|
||||
DrawWorld((DrawingHandleWorld) args.DrawingHandle, args);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -382,8 +408,8 @@ namespace Robust.Client.Debugging
|
||||
|
||||
if (edge.OneSided)
|
||||
{
|
||||
worldHandle.DrawCircle(v1, 0.5f, color);
|
||||
worldHandle.DrawCircle(v2, 0.5f, color);
|
||||
worldHandle.DrawCircle(v1, 0.1f, color);
|
||||
worldHandle.DrawCircle(v2, 0.1f, color);
|
||||
}
|
||||
|
||||
break;
|
||||
@@ -416,11 +442,45 @@ namespace Robust.Client.Debugging
|
||||
var p1 = matrix1.Transform(joint.LocalAnchorA);
|
||||
var p2 = matrix2.Transform(joint.LocalAnchorB);
|
||||
|
||||
var xfa = new Transform(xf1, xform1.WorldRotation);
|
||||
var xfb = new Transform(xf2, xform2.WorldRotation);
|
||||
|
||||
switch (joint)
|
||||
{
|
||||
case DistanceJoint:
|
||||
worldHandle.DrawLine(xf1, xf2, JointColor);
|
||||
break;
|
||||
case PrismaticJoint prisma:
|
||||
var pA = Transform.Mul(xfa, joint.LocalAnchorA);
|
||||
var pB = Transform.Mul(xfb, joint.LocalAnchorB);
|
||||
|
||||
var axis = Transform.Mul(xfa.Quaternion2D, prisma._localXAxisA);
|
||||
|
||||
Color c1 = new(0.7f, 0.7f, 0.7f);
|
||||
Color c2 = new(0.3f, 0.9f, 0.3f);
|
||||
Color c3 = new(0.9f, 0.3f, 0.3f);
|
||||
Color c4 = new(0.3f, 0.3f, 0.9f);
|
||||
Color c5 = new(0.4f, 0.4f, 0.4f);
|
||||
|
||||
worldHandle.DrawLine(pA, pB, c5);
|
||||
|
||||
if (prisma.EnableLimit)
|
||||
{
|
||||
var lower = pA + axis * prisma.LowerTranslation;
|
||||
var upper = pA + axis * prisma.UpperTranslation;
|
||||
var perp = Transform.Mul(xfa.Quaternion2D, prisma._localYAxisA);
|
||||
worldHandle.DrawLine(lower, upper, c1);
|
||||
worldHandle.DrawLine(lower - perp * 0.5f, lower + perp * 0.5f, c2);
|
||||
worldHandle.DrawLine(upper - perp * 0.5f, upper + perp * 0.5f, c3);
|
||||
}
|
||||
else
|
||||
{
|
||||
worldHandle.DrawLine(pA - axis * 1.0f, pA + axis * 1.0f, c1);
|
||||
}
|
||||
|
||||
worldHandle.DrawCircle(pA, 0.5f, c1);
|
||||
worldHandle.DrawCircle(pB, 0.5f, c4);
|
||||
break;
|
||||
default:
|
||||
worldHandle.DrawLine(xf1, p1, JointColor);
|
||||
worldHandle.DrawLine(p1, p2, JointColor);
|
||||
|
||||
@@ -198,6 +198,8 @@ namespace Robust.Client
|
||||
_client.ConnectToServer(LaunchState.ConnectEndpoint);
|
||||
}
|
||||
|
||||
ProgramShared.RunExecCommands(_console, _commandLineArgs?.ExecCommands);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -74,7 +74,8 @@ namespace Robust.Client.Graphics.Clyde
|
||||
case DEventWindowFocus(var args):
|
||||
OnWindowFocused?.Invoke(args);
|
||||
break;
|
||||
case DEventWindowResized(var args):
|
||||
case DEventWindowResized(var reg, var args):
|
||||
reg.Resized?.Invoke(args);
|
||||
OnWindowResized?.Invoke(args);
|
||||
break;
|
||||
}
|
||||
@@ -112,7 +113,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
reg.FramebufferSize,
|
||||
reg.Handle);
|
||||
|
||||
_eventDispatchQueue.Enqueue(new DEventWindowResized(eventArgs));
|
||||
_eventDispatchQueue.Enqueue(new DEventWindowResized(reg, eventArgs));
|
||||
}
|
||||
|
||||
private void SendWindowContentScaleChanged(WindowContentScaleEventArgs ev)
|
||||
@@ -151,7 +152,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private sealed record DEventWindowClosed(WindowReg Reg, WindowRequestClosedEventArgs Args) : DEventBase;
|
||||
|
||||
private sealed record DEventWindowResized(WindowResizedEventArgs Args) : DEventBase;
|
||||
private sealed record DEventWindowResized(WindowReg Reg, WindowResizedEventArgs Args) : DEventBase;
|
||||
|
||||
private sealed record DEventWindowContentScaleChanged(WindowContentScaleEventArgs Args) : DEventBase;
|
||||
|
||||
|
||||
@@ -458,6 +458,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public RenderWindow RenderTarget = default!;
|
||||
public Action<WindowRequestClosedEventArgs>? RequestClosed;
|
||||
public Action<WindowDestroyedEventArgs>? Closed;
|
||||
public Action<WindowResizedEventArgs>? Resized;
|
||||
}
|
||||
|
||||
private sealed class WindowHandle : IClydeWindowInternal
|
||||
@@ -523,6 +524,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
remove => Reg.Closed -= value;
|
||||
}
|
||||
|
||||
public event Action<WindowResizedEventArgs>? Resized
|
||||
{
|
||||
add => Reg.Resized += value;
|
||||
remove => Reg.Resized -= value;
|
||||
}
|
||||
|
||||
public nint? WindowsHWnd => _clyde._windowing!.WindowGetWin32Window(Reg);
|
||||
}
|
||||
|
||||
|
||||
@@ -627,6 +627,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
public bool DisposeOnClose { get; set; }
|
||||
public event Action<WindowRequestClosedEventArgs>? RequestClosed { add { } remove { } }
|
||||
public event Action<WindowDestroyedEventArgs>? Destroyed;
|
||||
public event Action<WindowResizedEventArgs>? Resized { add { } remove { } }
|
||||
|
||||
public void MaximizeOnMonitor(IClydeMonitor monitor)
|
||||
{
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared;
|
||||
using System.Threading;
|
||||
using OpenToolkit.GraphicsLibraryFramework;
|
||||
using Robust.Client.Input;
|
||||
@@ -8,6 +9,8 @@ using GlfwKey = OpenToolkit.GraphicsLibraryFramework.Keys;
|
||||
using GlfwButton = OpenToolkit.GraphicsLibraryFramework.MouseButton;
|
||||
using static Robust.Client.Input.Mouse;
|
||||
using static Robust.Client.Input.Keyboard;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Configuration;
|
||||
|
||||
namespace Robust.Client.Graphics.Clyde
|
||||
{
|
||||
@@ -22,6 +25,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
|
||||
private void InitKeyMap()
|
||||
{
|
||||
_printableKeyNameMap.Clear();
|
||||
// From GLFW's source code: this is the actual list of "printable" keys
|
||||
// that GetKeyName returns something for.
|
||||
CacheKey(Keys.KeyPadEqual);
|
||||
@@ -41,8 +45,18 @@ namespace Robust.Client.Graphics.Clyde
|
||||
if (rKey == Key.Unknown)
|
||||
return;
|
||||
|
||||
var name = GLFW.GetKeyName(key, 0);
|
||||
if (name != null)
|
||||
string name;
|
||||
|
||||
if (!_clyde._cfg.GetCVar(CVars.DisplayUSQWERTYHotkeys))
|
||||
{
|
||||
name = GLFW.GetKeyName(key, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
name = key.ToString();
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(name))
|
||||
_printableKeyNameMap.Add(rKey, name);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using System.Runtime.Serialization;
|
||||
using OpenToolkit.GraphicsLibraryFramework;
|
||||
using Robust.Client.Input;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -16,6 +17,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
[Dependency] private readonly ILogManager _logManager = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
[Dependency] private readonly ILocalizationManager _loc = default!;
|
||||
[Dependency] private readonly IInputManager _inputManager = default!;
|
||||
|
||||
private readonly Clyde _clyde;
|
||||
|
||||
@@ -39,6 +41,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
#if DEBUG
|
||||
_cfg.OnValueChanged(CVars.DisplayWin32Experience, b => _win32Experience = b, true);
|
||||
#endif
|
||||
_cfg.OnValueChanged(CVars.DisplayUSQWERTYHotkeys, ReInitKeyMap);
|
||||
|
||||
InitChannels();
|
||||
|
||||
@@ -60,6 +63,7 @@ namespace Robust.Client.Graphics.Clyde
|
||||
if (_glfwInitialized)
|
||||
{
|
||||
_sawmill.Debug("Terminating GLFW.");
|
||||
_cfg.UnsubValueChanged(CVars.DisplayUSQWERTYHotkeys, ReInitKeyMap);
|
||||
GLFW.Terminate();
|
||||
}
|
||||
}
|
||||
@@ -69,6 +73,12 @@ namespace Robust.Client.Graphics.Clyde
|
||||
// Not currently used
|
||||
}
|
||||
|
||||
private void ReInitKeyMap(bool onValueChanged)
|
||||
{
|
||||
InitKeyMap();
|
||||
_inputManager.InputModeChanged();
|
||||
}
|
||||
|
||||
private bool InitGlfw()
|
||||
{
|
||||
StoreCallbacks();
|
||||
|
||||
@@ -35,6 +35,12 @@ namespace Robust.Client.Graphics
|
||||
/// This means the window must not be used anymore (it is disposed).
|
||||
/// </summary>
|
||||
event Action<WindowDestroyedEventArgs> Destroyed;
|
||||
|
||||
/// <summary>
|
||||
/// Raised when the window has been definitively closed.
|
||||
/// This means the window must not be used anymore (it is disposed).
|
||||
/// </summary>
|
||||
event Action<WindowResizedEventArgs> Resized;
|
||||
}
|
||||
|
||||
public interface IClydeWindowInternal : IClydeWindow
|
||||
|
||||
@@ -111,6 +111,7 @@ namespace Robust.Client.Input
|
||||
|
||||
event Action<IKeyBinding> OnKeyBindingAdded;
|
||||
event Action<IKeyBinding> OnKeyBindingRemoved;
|
||||
event Action OnInputModeChanged;
|
||||
|
||||
/// <summary>
|
||||
/// Gets all the keybinds bound to a specific function.
|
||||
@@ -131,5 +132,7 @@ namespace Robust.Client.Input
|
||||
bool IsKeyFunctionModified(BoundKeyFunction function);
|
||||
|
||||
bool IsKeyDown(Keyboard.Key key);
|
||||
|
||||
void InputModeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ using System.Runtime.InteropServices;
|
||||
using System.Text;
|
||||
using JetBrains.Annotations;
|
||||
using Robust.Client.UserInterface;
|
||||
using Robust.Shared;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Input;
|
||||
@@ -43,6 +45,7 @@ namespace Robust.Client.Input
|
||||
[Dependency] private readonly IResourceManager _resourceMan = default!;
|
||||
[Dependency] private readonly IReflectionManager _reflectionManager = default!;
|
||||
[Dependency] private readonly IUserInterfaceManagerInternal _uiMgr = default!;
|
||||
[Dependency] private readonly IConfigurationManager _cfg = default!;
|
||||
|
||||
private bool _currentlyFindingViewport;
|
||||
|
||||
@@ -98,6 +101,7 @@ namespace Robust.Client.Input
|
||||
public event KeyEventAction? FirstChanceOnKeyEvent;
|
||||
public event Action<IKeyBinding>? OnKeyBindingAdded;
|
||||
public event Action<IKeyBinding>? OnKeyBindingRemoved;
|
||||
public event Action? OnInputModeChanged;
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Initialize()
|
||||
@@ -569,6 +573,8 @@ namespace Robust.Client.Input
|
||||
OnKeyBindingRemoved?.Invoke(binding);
|
||||
}
|
||||
|
||||
public void InputModeChanged() => OnInputModeChanged?.Invoke();
|
||||
|
||||
private void RegisterBinding(KeyBinding binding, bool markModified = true)
|
||||
{
|
||||
// we sort larger combos first so they take priority over smaller (single key) combos,
|
||||
|
||||
@@ -129,6 +129,7 @@ namespace Robust.Client.UserInterface.Controls
|
||||
ClydeWindow = _clyde.CreateWindow(parameters);
|
||||
ClydeWindow.RequestClosed += OnWindowRequestClosed;
|
||||
ClydeWindow.Destroyed += OnWindowDestroyed;
|
||||
ClydeWindow.Resized += OnWindowResized;
|
||||
|
||||
_root = UserInterfaceManager.CreateWindowRoot(ClydeWindow);
|
||||
_root.AddChild(this);
|
||||
@@ -167,6 +168,11 @@ namespace Robust.Client.UserInterface.Controls
|
||||
RealClosed();
|
||||
}
|
||||
|
||||
private void OnWindowResized(WindowResizedEventArgs obj)
|
||||
{
|
||||
SetSize = obj.NewSize;
|
||||
}
|
||||
|
||||
private void RealClosed()
|
||||
{
|
||||
Orphan();
|
||||
|
||||
@@ -83,6 +83,7 @@ namespace Robust.Server
|
||||
[Dependency] private readonly IRobustMappedStringSerializer _stringSerializer = default!;
|
||||
[Dependency] private readonly ILocalizationManagerInternal _loc = default!;
|
||||
[Dependency] private readonly INetConfigurationManager _netCfgMan = default!;
|
||||
[Dependency] private readonly IServerConsoleHost _consoleHost = default!;
|
||||
|
||||
private readonly Stopwatch _uptimeStopwatch = new();
|
||||
|
||||
@@ -337,7 +338,7 @@ namespace Robust.Server
|
||||
prototypeManager.LoadDirectory(Options.PrototypeDirectory);
|
||||
prototypeManager.Resync();
|
||||
|
||||
IoCManager.Resolve<IServerConsoleHost>().Initialize();
|
||||
_consoleHost.Initialize();
|
||||
_entityManager.Startup();
|
||||
_mapManager.Startup();
|
||||
IoCManager.Resolve<IEntityLookup>().Startup();
|
||||
@@ -372,6 +373,8 @@ namespace Robust.Server
|
||||
|
||||
GC.Collect();
|
||||
|
||||
ProgramShared.RunExecCommands(_consoleHost, _commandLineArgs?.ExecCommands);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,130 +4,138 @@ using Robust.Shared;
|
||||
using Robust.Shared.Utility;
|
||||
using C = System.Console;
|
||||
|
||||
namespace Robust.Server
|
||||
namespace Robust.Server;
|
||||
|
||||
internal sealed class CommandLineArgs
|
||||
{
|
||||
internal sealed class CommandLineArgs
|
||||
public MountOptions MountOptions { get; }
|
||||
public string? ConfigFile { get; }
|
||||
public string? DataDir { get; }
|
||||
public IReadOnlyCollection<(string key, string value)> CVars { get; }
|
||||
public IReadOnlyCollection<(string key, string value)> LogLevels { get; }
|
||||
public IReadOnlyList<string> ExecCommands { get; set; }
|
||||
|
||||
// Manual parser because C# has no good command line parsing libraries. Also dependencies bad.
|
||||
// Also I don't like spending 100ms parsing command line args. Do you?
|
||||
public static bool TryParse(IReadOnlyList<string> args, [NotNullWhen(true)] out CommandLineArgs? parsed)
|
||||
{
|
||||
public MountOptions MountOptions { get; }
|
||||
public string? ConfigFile { get; }
|
||||
public string? DataDir { get; }
|
||||
public IReadOnlyCollection<(string key, string value)> CVars { get; }
|
||||
public IReadOnlyCollection<(string key, string value)> LogLevels { get; }
|
||||
parsed = null;
|
||||
string? configFile = null;
|
||||
string? dataDir = null;
|
||||
var cvars = new List<(string, string)>();
|
||||
var logLevels = new List<(string, string)>();
|
||||
var mountOptions = new MountOptions();
|
||||
var execCommands = new List<string>();
|
||||
|
||||
// Manual parser because C# has no good command line parsing libraries. Also dependencies bad.
|
||||
// Also I don't like spending 100ms parsing command line args. Do you?
|
||||
public static bool TryParse(IReadOnlyList<string> args, [NotNullWhen(true)] out CommandLineArgs? parsed)
|
||||
using var enumerator = args.GetEnumerator();
|
||||
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
parsed = null;
|
||||
string? configFile = null;
|
||||
string? dataDir = null;
|
||||
var cvars = new List<(string, string)>();
|
||||
var logLevels = new List<(string, string)>();
|
||||
var mountOptions = new MountOptions();
|
||||
|
||||
using var enumerator = args.GetEnumerator();
|
||||
|
||||
while (enumerator.MoveNext())
|
||||
var arg = enumerator.Current;
|
||||
if (arg == "--config-file")
|
||||
{
|
||||
var arg = enumerator.Current;
|
||||
if (arg == "--config-file")
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing config file.");
|
||||
return false;
|
||||
}
|
||||
|
||||
configFile = enumerator.Current;
|
||||
}
|
||||
else if (arg == "--data-dir")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing data directory.");
|
||||
return false;
|
||||
}
|
||||
|
||||
dataDir = enumerator.Current;
|
||||
}
|
||||
else if (arg == "--cvar")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing cvar value.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var cvar = enumerator.Current;
|
||||
DebugTools.AssertNotNull(cvar);
|
||||
var pos = cvar.IndexOf('=');
|
||||
|
||||
if (pos == -1)
|
||||
{
|
||||
C.WriteLine("Expected = in cvar.");
|
||||
return false;
|
||||
}
|
||||
|
||||
cvars.Add((cvar[..pos], cvar[(pos + 1)..]));
|
||||
}
|
||||
else if (arg == "--help")
|
||||
{
|
||||
PrintHelp();
|
||||
C.WriteLine("Missing config file.");
|
||||
return false;
|
||||
}
|
||||
else if (arg == "--mount-zip")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing mount path");
|
||||
return false;
|
||||
}
|
||||
|
||||
mountOptions.ZipMounts.Add(enumerator.Current);
|
||||
}
|
||||
else if (arg == "--mount-dir")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing mount path");
|
||||
return false;
|
||||
}
|
||||
|
||||
mountOptions.DirMounts.Add(enumerator.Current);
|
||||
}
|
||||
else if (arg == "--loglevel")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing loglevel sawmill.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var loglevel = enumerator.Current;
|
||||
DebugTools.AssertNotNull(loglevel);
|
||||
var pos = loglevel.IndexOf('=');
|
||||
|
||||
if (pos == -1)
|
||||
{
|
||||
C.WriteLine("Expected = in loglevel.");
|
||||
return false;
|
||||
}
|
||||
|
||||
logLevels.Add((loglevel[..pos], loglevel[(pos + 1)..]));
|
||||
}
|
||||
else
|
||||
{
|
||||
C.WriteLine("Unknown argument: {0}", arg);
|
||||
}
|
||||
configFile = enumerator.Current;
|
||||
}
|
||||
else if (arg == "--data-dir")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing data directory.");
|
||||
return false;
|
||||
}
|
||||
|
||||
parsed = new CommandLineArgs(configFile, dataDir, cvars, logLevels, mountOptions);
|
||||
return true;
|
||||
dataDir = enumerator.Current;
|
||||
}
|
||||
else if (arg == "--cvar")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing cvar value.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var cvar = enumerator.Current;
|
||||
DebugTools.AssertNotNull(cvar);
|
||||
var pos = cvar.IndexOf('=');
|
||||
|
||||
if (pos == -1)
|
||||
{
|
||||
C.WriteLine("Expected = in cvar.");
|
||||
return false;
|
||||
}
|
||||
|
||||
cvars.Add((cvar[..pos], cvar[(pos + 1)..]));
|
||||
}
|
||||
else if (arg == "--help")
|
||||
{
|
||||
PrintHelp();
|
||||
return false;
|
||||
}
|
||||
else if (arg == "--mount-zip")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing mount path");
|
||||
return false;
|
||||
}
|
||||
|
||||
mountOptions.ZipMounts.Add(enumerator.Current);
|
||||
}
|
||||
else if (arg == "--mount-dir")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing mount path");
|
||||
return false;
|
||||
}
|
||||
|
||||
mountOptions.DirMounts.Add(enumerator.Current);
|
||||
}
|
||||
else if (arg == "--loglevel")
|
||||
{
|
||||
if (!enumerator.MoveNext())
|
||||
{
|
||||
C.WriteLine("Missing loglevel sawmill.");
|
||||
return false;
|
||||
}
|
||||
|
||||
var loglevel = enumerator.Current;
|
||||
DebugTools.AssertNotNull(loglevel);
|
||||
var pos = loglevel.IndexOf('=');
|
||||
|
||||
if (pos == -1)
|
||||
{
|
||||
C.WriteLine("Expected = in loglevel.");
|
||||
return false;
|
||||
}
|
||||
|
||||
logLevels.Add((loglevel[..pos], loglevel[(pos + 1)..]));
|
||||
}
|
||||
else if (arg.StartsWith("+"))
|
||||
{
|
||||
execCommands.Add(arg[1..]);
|
||||
}
|
||||
else
|
||||
{
|
||||
C.WriteLine("Unknown argument: {0}", arg);
|
||||
}
|
||||
}
|
||||
|
||||
private static void PrintHelp()
|
||||
{
|
||||
C.WriteLine(@"
|
||||
parsed = new CommandLineArgs(configFile, dataDir, cvars, logLevels, mountOptions, execCommands);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void PrintHelp()
|
||||
{
|
||||
C.WriteLine(@"
|
||||
Usage: Robust.Server [options] [+command [+command]]
|
||||
|
||||
Options:
|
||||
--config-file Path to the config file to read from.
|
||||
--data-dir Path to the data directory to read/write from/to.
|
||||
@@ -136,16 +144,25 @@ Options:
|
||||
--mount-dir Resource directory to mount.
|
||||
--mount-zip Resource zip to mount.
|
||||
--help Display this help text and exit.
|
||||
");
|
||||
}
|
||||
|
||||
private CommandLineArgs(string? configFile, string? dataDir, IReadOnlyCollection<(string, string)> cVars, IReadOnlyCollection<(string, string)> logLevels, MountOptions mountOptions)
|
||||
{
|
||||
ConfigFile = configFile;
|
||||
DataDir = dataDir;
|
||||
CVars = cVars;
|
||||
LogLevels = logLevels;
|
||||
MountOptions = mountOptions;
|
||||
}
|
||||
+command: You can pass a set of commands, prefixed by +,
|
||||
to be executed in the console in order after the game has finished initializing.
|
||||
");
|
||||
}
|
||||
|
||||
private CommandLineArgs(
|
||||
string? configFile,
|
||||
string? dataDir,
|
||||
IReadOnlyCollection<(string, string)> cVars,
|
||||
IReadOnlyCollection<(string, string)> logLevels,
|
||||
MountOptions mountOptions,
|
||||
IReadOnlyList<string> execCommands)
|
||||
{
|
||||
ConfigFile = configFile;
|
||||
DataDir = dataDir;
|
||||
CVars = cVars;
|
||||
LogLevels = logLevels;
|
||||
MountOptions = mountOptions;
|
||||
ExecCommands = execCommands;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,7 +67,7 @@ namespace Robust.Server.GameObjects
|
||||
{
|
||||
var entity = base.CreateEntity(prototypeName, uid);
|
||||
|
||||
if (prototypeName != null)
|
||||
if (!string.IsNullOrWhiteSpace(prototypeName))
|
||||
{
|
||||
var prototype = PrototypeManager.Index<EntityPrototype>(prototypeName);
|
||||
|
||||
|
||||
@@ -15,7 +15,6 @@ namespace Robust.Server.GameStates
|
||||
/// </summary>
|
||||
void SendGameStateUpdate();
|
||||
|
||||
bool PvsEnabled { get; set; }
|
||||
float PvsRange { get; set; }
|
||||
void SetTransformNetId(ushort netId);
|
||||
}
|
||||
|
||||
94
Robust.Server/GameStates/PVSSystem.Dirty.cs
Normal file
94
Robust.Server/GameStates/PVSSystem.Dirty.cs
Normal file
@@ -0,0 +1,94 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Robust.Server.Player;
|
||||
using Robust.Shared.Enums;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.GameStates
|
||||
{
|
||||
/// <summary>
|
||||
/// Caching for dirty bodies
|
||||
/// </summary>
|
||||
internal partial class PVSSystem
|
||||
{
|
||||
private const int DirtyBufferSize = 4;
|
||||
|
||||
/// <summary>
|
||||
/// if it's a new entity we need to GetEntityState from tick 0.
|
||||
/// </summary>
|
||||
private HashSet<EntityUid>[] _addEntities = new HashSet<EntityUid>[DirtyBufferSize];
|
||||
private HashSet<EntityUid>[] _dirtyEntities = new HashSet<EntityUid>[DirtyBufferSize];
|
||||
private int _currentIndex = 1;
|
||||
|
||||
private void InitializeDirty()
|
||||
{
|
||||
for (var i = 0; i < DirtyBufferSize; i++)
|
||||
{
|
||||
_addEntities[i] = new HashSet<EntityUid>(32);
|
||||
_dirtyEntities[i] = new HashSet<EntityUid>(32);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnEntityAdd(object? sender, EntityUid e)
|
||||
{
|
||||
DebugTools.Assert(_currentIndex == _gameTiming.CurTick.Value % DirtyBufferSize);
|
||||
_addEntities[_currentIndex].Add(e);
|
||||
}
|
||||
|
||||
private void OnDirty(ref EntityDirtyEvent ev)
|
||||
{
|
||||
if (_addEntities[_currentIndex].Contains(ev.Uid) ||
|
||||
EntityManager.GetComponent<MetaDataComponent>(ev.Uid).EntityLifeStage < EntityLifeStage.Initialized) return;
|
||||
|
||||
_dirtyEntities[_currentIndex].Add(ev.Uid);
|
||||
}
|
||||
|
||||
private void CleanupDirty(IEnumerable<IPlayerSession> sessions)
|
||||
{
|
||||
// Just in case we desync somehow
|
||||
_currentIndex = ((int) _gameTiming.CurTick.Value + 1) % DirtyBufferSize;
|
||||
_addEntities[_currentIndex].Clear();
|
||||
_dirtyEntities[_currentIndex].Clear();
|
||||
|
||||
if (!CullingEnabled)
|
||||
{
|
||||
foreach (var player in sessions)
|
||||
{
|
||||
if (player.Status != SessionStatus.InGame)
|
||||
{
|
||||
_oldPlayers.Remove(player);
|
||||
}
|
||||
else
|
||||
{
|
||||
_oldPlayers.Add(player);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool TryGetTick(GameTick tick, [NotNullWhen(true)] out HashSet<EntityUid>? addEntities, [NotNullWhen(true)] out HashSet<EntityUid>? dirtyEntities)
|
||||
{
|
||||
var currentTick = _gameTiming.CurTick;
|
||||
if (currentTick.Value - tick.Value >= DirtyBufferSize)
|
||||
{
|
||||
addEntities = null;
|
||||
dirtyEntities = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
var index = tick.Value % DirtyBufferSize;
|
||||
if (index > _dirtyEntities.Length - 1)
|
||||
{
|
||||
addEntities = null;
|
||||
dirtyEntities = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
addEntities = _addEntities[index];
|
||||
dirtyEntities = _dirtyEntities[index];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
135
Robust.Server/GameStates/PVSSystem.States.cs
Normal file
135
Robust.Server/GameStates/PVSSystem.States.cs
Normal file
@@ -0,0 +1,135 @@
|
||||
using System.Collections.Generic;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Players;
|
||||
using Robust.Shared.Timing;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.GameStates
|
||||
{
|
||||
internal partial class PVSSystem
|
||||
{
|
||||
/// <summary>
|
||||
/// Keep around a cache of players who don't need every entity state dumped on them where culling disabled.
|
||||
/// </summary>
|
||||
private HashSet<ICommonSession> _oldPlayers = new();
|
||||
|
||||
/// <summary>
|
||||
/// Generates a network entity state for the given entity.
|
||||
/// </summary>
|
||||
/// <param name="player">The player to generate this state for.</param>
|
||||
/// <param name="entityUid">Uid of the entity to generate the state from.</param>
|
||||
/// <param name="fromTick">Only provide delta changes from this tick.</param>
|
||||
/// <returns>New entity State for the given entity.</returns>
|
||||
private EntityState GetEntityState(ICommonSession player, EntityUid entityUid, GameTick fromTick)
|
||||
{
|
||||
var bus = _entMan.EventBus;
|
||||
var changed = new List<ComponentChange>();
|
||||
|
||||
foreach (var (netId, component) in _entMan.GetNetComponents(entityUid))
|
||||
{
|
||||
DebugTools.Assert(component.Initialized);
|
||||
|
||||
// NOTE: When LastModifiedTick or CreationTick are 0 it means that the relevant data is
|
||||
// "not different from entity creation".
|
||||
// i.e. when the client spawns the entity and loads the entity prototype,
|
||||
// the data it deserializes from the prototype SHOULD be equal
|
||||
// to what the component state / ComponentChange would send.
|
||||
// As such, we can avoid sending this data in this case since the client "already has it".
|
||||
|
||||
DebugTools.Assert(component.LastModifiedTick >= component.CreationTick);
|
||||
|
||||
if (component.CreationTick != GameTick.Zero && component.CreationTick >= fromTick && !component.Deleted)
|
||||
{
|
||||
ComponentState? state = null;
|
||||
if (component.NetSyncEnabled && component.LastModifiedTick != GameTick.Zero && component.LastModifiedTick >= fromTick)
|
||||
state = _entMan.GetComponentState(bus, component, player);
|
||||
|
||||
// Can't be null since it's returned by GetNetComponents
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
changed.Add(ComponentChange.Added(netId, state));
|
||||
}
|
||||
else if (component.NetSyncEnabled && component.LastModifiedTick != GameTick.Zero && component.LastModifiedTick >= fromTick)
|
||||
{
|
||||
changed.Add(ComponentChange.Changed(netId, _entMan.GetComponentState(bus, component, player)));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var netId in _entMan.GetDeletedComponents(entityUid, fromTick))
|
||||
{
|
||||
changed.Add(ComponentChange.Removed(netId));
|
||||
}
|
||||
|
||||
return new EntityState(entityUid, changed.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all entity states that have been modified after and including the provided tick.
|
||||
/// </summary>
|
||||
private List<EntityState>? GetAllEntityStates(ICommonSession player, GameTick fromTick, GameTick toTick)
|
||||
{
|
||||
List<EntityState> stateEntities;
|
||||
|
||||
if (_oldPlayers.Contains(player))
|
||||
{
|
||||
stateEntities = new List<EntityState>();
|
||||
var seenEnts = new HashSet<EntityUid>();
|
||||
var slowPath = false;
|
||||
|
||||
for (var i = fromTick.Value; i <= toTick.Value; i++)
|
||||
{
|
||||
var tick = new GameTick(i);
|
||||
if (!TryGetTick(tick, out var add, out var dirty))
|
||||
{
|
||||
slowPath = true;
|
||||
break;
|
||||
}
|
||||
|
||||
foreach (var uid in add)
|
||||
{
|
||||
if (!seenEnts.Add(uid) || !_entMan.TryGetEntity(uid, out var entity) || entity.Deleted) continue;
|
||||
|
||||
DebugTools.Assert(entity.Initialized);
|
||||
|
||||
if (entity.LastModifiedTick >= fromTick)
|
||||
stateEntities.Add(GetEntityState(player, entity.Uid, GameTick.Zero));
|
||||
}
|
||||
|
||||
foreach (var uid in dirty)
|
||||
{
|
||||
DebugTools.Assert(!add.Contains(uid));
|
||||
|
||||
if (!seenEnts.Add(uid) || !_entMan.TryGetEntity(uid, out var entity) || entity.Deleted) continue;
|
||||
|
||||
DebugTools.Assert(entity.Initialized);
|
||||
|
||||
if (entity.LastModifiedTick >= fromTick)
|
||||
stateEntities.Add(GetEntityState(player, entity.Uid, fromTick));
|
||||
}
|
||||
}
|
||||
|
||||
if (!slowPath)
|
||||
{
|
||||
return stateEntities.Count == 0 ? default : stateEntities;
|
||||
}
|
||||
}
|
||||
|
||||
stateEntities = new List<EntityState>(EntityManager.EntityCount);
|
||||
|
||||
foreach (var entity in _entMan.GetEntities())
|
||||
{
|
||||
if (entity.Deleted)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
DebugTools.Assert(entity.Initialized);
|
||||
|
||||
if (entity.LastModifiedTick >= fromTick)
|
||||
stateEntities.Add(GetEntityState(player, entity.Uid, fromTick));
|
||||
}
|
||||
|
||||
// no point sending an empty collection
|
||||
return stateEntities.Count == 0 ? default : stateEntities;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Composition;
|
||||
using System.Runtime.CompilerServices;
|
||||
using Microsoft.Extensions.ObjectPool;
|
||||
using Robust.Server.GameObjects;
|
||||
@@ -18,7 +17,7 @@ using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Server.GameStates
|
||||
{
|
||||
internal sealed class PVSSystem : EntitySystem
|
||||
internal sealed partial class PVSSystem : EntitySystem
|
||||
{
|
||||
private const int ViewSetCapacity = 256; // starting number of entities that are in view
|
||||
private const int PlayerSetSize = 64; // Starting number of players
|
||||
@@ -113,6 +112,7 @@ namespace Robust.Server.GameStates
|
||||
{
|
||||
base.Initialize();
|
||||
EntityManager.EntityDeleted += OnEntityDelete;
|
||||
EntityManager.EntityAdded += OnEntityAdd;
|
||||
_playerManager.PlayerStatusChanged += OnPlayerStatusChange;
|
||||
_mapManager.OnGridRemoved += OnGridRemoved;
|
||||
|
||||
@@ -120,6 +120,16 @@ namespace Robust.Server.GameStates
|
||||
// plus invalidate any chunks currently being streamed as well.
|
||||
StreamingTilesPerTick = (int) (_configManager.GetCVar(CVars.StreamedTilesPerSecond) / _gameTiming.TickRate);
|
||||
_configManager.OnValueChanged(CVars.StreamedTileRange, SetStreamRange, true);
|
||||
_configManager.OnValueChanged(CVars.NetPVS, SetPvs, true);
|
||||
|
||||
SubscribeLocalEvent<EntityDirtyEvent>(OnDirty);
|
||||
|
||||
InitializeDirty();
|
||||
}
|
||||
|
||||
public void Cleanup(IEnumerable<IPlayerSession> sessions)
|
||||
{
|
||||
CleanupDirty(sessions);
|
||||
}
|
||||
|
||||
private void SetStreamRange(float value)
|
||||
@@ -127,13 +137,25 @@ namespace Robust.Server.GameStates
|
||||
StreamRange = value;
|
||||
}
|
||||
|
||||
private void SetPvs(bool value)
|
||||
{
|
||||
CullingEnabled = value;
|
||||
|
||||
if (CullingEnabled)
|
||||
{
|
||||
_oldPlayers.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
EntityManager.EntityDeleted -= OnEntityDelete;
|
||||
EntityManager.EntityAdded -= OnEntityAdd;
|
||||
_playerManager.PlayerStatusChanged -= OnPlayerStatusChange;
|
||||
_mapManager.OnGridRemoved -= OnGridRemoved;
|
||||
_configManager.UnsubValueChanged(CVars.StreamedTileRange, SetStreamRange);
|
||||
_configManager.UnsubValueChanged(CVars.NetPVS, SetPvs);
|
||||
}
|
||||
|
||||
private void OnGridRemoved(MapId mapid, GridId gridid)
|
||||
@@ -181,6 +203,7 @@ namespace Robust.Server.GameStates
|
||||
_playerChunks.Remove(session);
|
||||
_playerLastFullMap.Remove(session, out _);
|
||||
_streamingChunks.Remove(session);
|
||||
_oldPlayers.Remove(session);
|
||||
}
|
||||
|
||||
#endregion
|
||||
@@ -220,7 +243,7 @@ namespace Robust.Server.GameStates
|
||||
List<EntityUid>? deletions;
|
||||
if (!CullingEnabled)
|
||||
{
|
||||
var allStates = ServerGameStateManager.GetAllEntityStates(_entMan, session, fromTick);
|
||||
var allStates = GetAllEntityStates(session, fromTick, toTick);
|
||||
deletions = GetDeletedEntities(fromTick);
|
||||
_playerLastFullMap.AddOrUpdate(session, toTick, (_, _) => toTick);
|
||||
return (allStates, deletions);
|
||||
@@ -355,7 +378,7 @@ namespace Robust.Server.GameStates
|
||||
if (ent.EntityLastModifiedTick < lastSeenChunk)
|
||||
return;
|
||||
|
||||
var newState = ServerGameStateManager.GetEntityState(_entMan, session, uid, lastSeenChunk);
|
||||
var newState = GetEntityState(session, uid, lastSeenChunk);
|
||||
entityStates.Add(newState);
|
||||
});
|
||||
|
||||
@@ -390,7 +413,7 @@ namespace Robust.Server.GameStates
|
||||
|
||||
foreach (var anchoredEnt in chunk.GetSnapGridCell(x, y))
|
||||
{
|
||||
var newState = ServerGameStateManager.GetEntityState(_entMan, session, anchoredEnt, GameTick.Zero);
|
||||
var newState = GetEntityState(session, anchoredEnt, GameTick.Zero);
|
||||
entityStates.Add(newState);
|
||||
}
|
||||
}
|
||||
@@ -512,7 +535,7 @@ namespace Robust.Server.GameStates
|
||||
continue;
|
||||
|
||||
// only send new changes
|
||||
var newState = ServerGameStateManager.GetEntityState(_entMan, session, entityUid, fromTick);
|
||||
var newState = GetEntityState(session, entityUid, fromTick);
|
||||
|
||||
if (!newState.Empty)
|
||||
entityStates.Add(newState);
|
||||
@@ -522,7 +545,7 @@ namespace Robust.Server.GameStates
|
||||
// PVS enter message
|
||||
|
||||
// don't assume the client knows anything about us
|
||||
var newState = ServerGameStateManager.GetEntityState(_entMan, session, entityUid, GameTick.Zero);
|
||||
var newState = GetEntityState(session, entityUid, GameTick.Zero);
|
||||
entityStates.Add(newState);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
@@ -42,12 +43,6 @@ namespace Robust.Server.GameStates
|
||||
|
||||
private ISawmill _logger = default!;
|
||||
|
||||
public bool PvsEnabled
|
||||
{
|
||||
get => _configurationManager.GetCVar(CVars.NetPVS);
|
||||
set => _configurationManager.SetCVar(CVars.NetPVS, value);
|
||||
}
|
||||
|
||||
public float PvsRange
|
||||
{
|
||||
get => _configurationManager.GetCVar(CVars.NetMaxUpdateRange);
|
||||
@@ -122,7 +117,6 @@ namespace Robust.Server.GameStates
|
||||
DebugTools.Assert(_networkManager.IsServer);
|
||||
|
||||
_pvs.ViewSize = PvsRange * 2;
|
||||
_pvs.CullingEnabled = PvsEnabled;
|
||||
|
||||
if (!_networkManager.IsConnected)
|
||||
{
|
||||
@@ -130,6 +124,7 @@ namespace Robust.Server.GameStates
|
||||
_entityManager.CullDeletionHistory(GameTick.MaxValue);
|
||||
_pvs.CullDeletionHistory(GameTick.MaxValue);
|
||||
_mapManager.CullDeletionHistory(GameTick.MaxValue);
|
||||
_pvs.Cleanup(_playerManager.GetAllPlayers());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -202,6 +197,7 @@ namespace Robust.Server.GameStates
|
||||
}
|
||||
});
|
||||
|
||||
_pvs.Cleanup(_playerManager.GetAllPlayers());
|
||||
var oldestAck = new GameTick(oldestAckValue);
|
||||
|
||||
// keep the deletion history buffers clean
|
||||
@@ -213,78 +209,5 @@ namespace Robust.Server.GameStates
|
||||
_mapManager.CullDeletionHistory(oldestAck);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generates a network entity state for the given entity.
|
||||
/// </summary>
|
||||
/// <param name="entMan">EntityManager that contains the entity.</param>
|
||||
/// <param name="player">The player to generate this state for.</param>
|
||||
/// <param name="entityUid">Uid of the entity to generate the state from.</param>
|
||||
/// <param name="fromTick">Only provide delta changes from this tick.</param>
|
||||
/// <returns>New entity State for the given entity.</returns>
|
||||
internal static EntityState GetEntityState(IServerEntityManager entMan, ICommonSession player, EntityUid entityUid, GameTick fromTick)
|
||||
{
|
||||
var bus = entMan.EventBus;
|
||||
var changed = new List<ComponentChange>();
|
||||
|
||||
foreach (var (netId, component) in entMan.GetNetComponents(entityUid))
|
||||
{
|
||||
DebugTools.Assert(component.Initialized);
|
||||
|
||||
// NOTE: When LastModifiedTick or CreationTick are 0 it means that the relevant data is
|
||||
// "not different from entity creation".
|
||||
// i.e. when the client spawns the entity and loads the entity prototype,
|
||||
// the data it deserializes from the prototype SHOULD be equal
|
||||
// to what the component state / ComponentChange would send.
|
||||
// As such, we can avoid sending this data in this case since the client "already has it".
|
||||
|
||||
DebugTools.Assert(component.LastModifiedTick >= component.CreationTick);
|
||||
|
||||
if (component.CreationTick != GameTick.Zero && component.CreationTick >= fromTick && !component.Deleted)
|
||||
{
|
||||
ComponentState? state = null;
|
||||
if (component.NetSyncEnabled && component.LastModifiedTick != GameTick.Zero && component.LastModifiedTick >= fromTick)
|
||||
state = entMan.GetComponentState(bus, component, player);
|
||||
|
||||
// Can't be null since it's returned by GetNetComponents
|
||||
// ReSharper disable once PossibleInvalidOperationException
|
||||
changed.Add(ComponentChange.Added(netId, state));
|
||||
}
|
||||
else if (component.NetSyncEnabled && component.LastModifiedTick != GameTick.Zero && component.LastModifiedTick >= fromTick)
|
||||
{
|
||||
changed.Add(ComponentChange.Changed(netId, entMan.GetComponentState(bus, component, player)));
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var netId in entMan.GetDeletedComponents(entityUid, fromTick))
|
||||
{
|
||||
changed.Add(ComponentChange.Removed(netId));
|
||||
}
|
||||
|
||||
return new EntityState(entityUid, changed.ToArray());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets all entity states that have been modified after and including the provided tick.
|
||||
/// </summary>
|
||||
internal static List<EntityState>? GetAllEntityStates(IServerEntityManager entityMan, ICommonSession player, GameTick fromTick)
|
||||
{
|
||||
var stateEntities = new List<EntityState>();
|
||||
foreach (var entity in entityMan.GetEntities())
|
||||
{
|
||||
if (entity.Deleted)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
DebugTools.Assert(entity.Initialized);
|
||||
|
||||
if (entity.LastModifiedTick >= fromTick)
|
||||
stateEntities.Add(GetEntityState(entityMan, player, entity.Uid, fromTick));
|
||||
}
|
||||
|
||||
// no point sending an empty collection
|
||||
return stateEntities.Count == 0 ? default : stateEntities;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
154
Robust.Shared.Maths/Matrix33.cs
Normal file
154
Robust.Shared.Maths/Matrix33.cs
Normal file
@@ -0,0 +1,154 @@
|
||||
// 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.
|
||||
|
||||
namespace Robust.Shared.Maths
|
||||
{
|
||||
public struct Matrix33
|
||||
{
|
||||
public Vector3 EX;
|
||||
public Vector3 EY;
|
||||
public Vector3 EZ;
|
||||
|
||||
public Matrix33(Vector3 c1, Vector3 c2, Vector3 c3)
|
||||
{
|
||||
EX = c1;
|
||||
EY = c2;
|
||||
EZ = c3;
|
||||
}
|
||||
|
||||
public void SetZero()
|
||||
{
|
||||
EX = Vector3.Zero;
|
||||
EY = Vector3.Zero;
|
||||
EZ = Vector3.Zero;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Solve A * x = b, where b is a column vector. This is more efficient
|
||||
/// than computing the inverse in one-shot cases.
|
||||
/// </summary>
|
||||
/// <param name="b"></param>
|
||||
/// <returns></returns>
|
||||
public Vector3 Solve33(Vector3 b)
|
||||
{
|
||||
float det = Vector3.Dot(EX, Vector3.Cross(EY, EZ));
|
||||
|
||||
if (det != 0.0f)
|
||||
{
|
||||
det = 1.0f / det;
|
||||
}
|
||||
|
||||
Vector3 x;
|
||||
x.X = det * Vector3.Dot(b, Vector3.Cross(EY, EZ));
|
||||
x.Y = det * Vector3.Dot(EX, Vector3.Cross(b, EZ));
|
||||
x.Z = det * Vector3.Dot(EX, Vector3.Cross(EY, b));
|
||||
return x;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Solve A * x = b, where b is a column vector. This is more efficient
|
||||
/// than computing the inverse in one-shot cases. Solve only the upper
|
||||
/// 2-by-2 matrix equation.
|
||||
/// </summary>
|
||||
/// <param name="b"></param>
|
||||
/// <returns></returns>
|
||||
public Vector2 Solve22(Vector2 b)
|
||||
{
|
||||
float a11 = EX.X, a12 = EY.X, a21 = EX.Y, a22 = EY.Y;
|
||||
float det = a11 * a22 - a12 * a21;
|
||||
if (det != 0.0f)
|
||||
{
|
||||
det = 1.0f / det;
|
||||
}
|
||||
|
||||
Vector2 x;
|
||||
x.X = det * (a22 * b.X - a12 * b.Y);
|
||||
x.Y = det * (a11 * b.Y - a21 * b.X);
|
||||
return x;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the inverse of this matrix as a 2-by-2.
|
||||
/// Returns the zero matrix if singular.
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public void GetInverse22(ref Matrix33 matrix)
|
||||
{
|
||||
float a = EX.X, b = EY.X, c = EX.Y, d = EY.Y;
|
||||
float det = a * d - b * c;
|
||||
if (det != 0.0f)
|
||||
{
|
||||
det = 1.0f / det;
|
||||
}
|
||||
|
||||
matrix.EX.X = det * d; matrix.EY.X = -det * b; matrix.EX.Z = 0.0f;
|
||||
matrix.EX.Y = -det * c; matrix.EY.Y = det * a; matrix.EY.Z = 0.0f;
|
||||
matrix.EZ.X = 0.0f; matrix.EZ.Y = 0.0f; matrix.EZ.Z = 0.0f;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the symmetric inverse of this matrix as a 3-by-3.
|
||||
/// Returns the zero matrix if singular.
|
||||
/// </summary>
|
||||
/// <param name="mm"></param>
|
||||
public void GetSymInverse33(ref Matrix33 matrix)
|
||||
{
|
||||
float det = Vector3.Dot(EX, Vector3.Cross(EY, EZ));
|
||||
if (det != 0.0f)
|
||||
{
|
||||
det = 1.0f / det;
|
||||
}
|
||||
|
||||
float a11 = EX.X, a12 = EY.X, a13 = EZ.X;
|
||||
float a22 = EY.Y, a23 = EZ.Y;
|
||||
float a33 = EZ.Z;
|
||||
|
||||
matrix.EX.X = det * (a22 * a33 - a23 * a23);
|
||||
matrix.EX.Y = det * (a13 * a23 - a12 * a33);
|
||||
matrix.EX.Z = det * (a12 * a23 - a13 * a22);
|
||||
|
||||
matrix.EY.X = matrix.EX.Y;
|
||||
matrix.EY.Y = det * (a11 * a33 - a13 * a13);
|
||||
matrix.EY.Z = det * (a13 * a12 - a11 * a23);
|
||||
|
||||
matrix.EZ.X = matrix.EX.Z;
|
||||
matrix.EZ.Y = matrix.EY.Z;
|
||||
matrix.EZ.Z = det * (a11 * a22 - a12 * a12);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiply a matrix times a vector.
|
||||
/// </summary>
|
||||
public Vector3 Mul(Vector3 v)
|
||||
{
|
||||
return v.X * EX + v.Y * EY + v.Z * EZ;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Multiply a matrix times a vector.
|
||||
/// </summary>
|
||||
public Vector2 Mul22(Vector2 v)
|
||||
{
|
||||
return new Vector2(EX.X * v.X + EY.X * v.Y, EX.Y * v.X + EY.Y * v.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,10 +103,16 @@ namespace Robust.Shared
|
||||
|
||||
public static readonly CVarDef<float> ResendHandshakeInterval =
|
||||
CVarDef.Create("net.handshake_interval", 3.0f, CVar.ARCHIVE | CVar.CLIENTONLY);
|
||||
|
||||
|
||||
public static readonly CVarDef<int> MaximumHandshakeAttempts =
|
||||
CVarDef.Create("net.handshake_attempts", 5, CVar.ARCHIVE | CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// If true, encrypt connections when possible.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> NetEncrypt =
|
||||
CVarDef.Create("net.encrypt", true, CVar.CLIENTONLY);
|
||||
|
||||
/**
|
||||
* SUS
|
||||
*/
|
||||
@@ -439,6 +445,12 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<string> DisplaySplashLogo =
|
||||
CVarDef.Create("display.splash_logo", "", CVar.CLIENTONLY);
|
||||
|
||||
/// <summary>
|
||||
/// Use US QWERTY hotkeys.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<bool> DisplayUSQWERTYHotkeys =
|
||||
CVarDef.Create("display.use_US_QWERTY_hotkeys", false, CVar.CLIENTONLY | CVar.ARCHIVE);
|
||||
|
||||
/*
|
||||
* AUDIO
|
||||
*/
|
||||
@@ -479,7 +491,7 @@ namespace Robust.Shared
|
||||
|
||||
// - Sleep
|
||||
public static readonly CVarDef<float> AngularSleepTolerance =
|
||||
CVarDef.Create("physics.angsleeptol", 0.25f / 180.0f * MathF.PI);
|
||||
CVarDef.Create("physics.angsleeptol", 0.3f / 180.0f * MathF.PI);
|
||||
|
||||
public static readonly CVarDef<float> LinearSleepTolerance =
|
||||
CVarDef.Create("physics.linsleeptol", 0.1f);
|
||||
@@ -533,23 +545,6 @@ namespace Robust.Shared
|
||||
public static readonly CVarDef<float> Baumgarte =
|
||||
CVarDef.Create("physics.baumgarte", 0.2f);
|
||||
|
||||
/// <summary>
|
||||
/// A small length used as a collision and constraint tolerance. Usually it is
|
||||
/// chosen to be numerically significant, but visually insignificant.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Note that some joints may have this cached and not update on value change.
|
||||
/// </remarks>
|
||||
public static readonly CVarDef<float> LinearSlop =
|
||||
CVarDef.Create("physics.linearslop", 0.005f);
|
||||
|
||||
/// <summary>
|
||||
/// A small angle used as a collision and constraint tolerance. Usually it is
|
||||
/// chosen to be numerically significant, but visually insignificant.
|
||||
/// </summary>
|
||||
public static readonly CVarDef<float> AngularSlop =
|
||||
CVarDef.Create("physics.angularslop", 2.0f / 180.0f * MathF.PI);
|
||||
|
||||
/// <summary>
|
||||
/// If true, it will run a GiftWrap convex hull on all polygon inputs.
|
||||
/// This makes for a more stable engine when given random input,
|
||||
|
||||
@@ -589,7 +589,7 @@ namespace Robust.Shared.GameObjects
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Inertia
|
||||
{
|
||||
get => _inertia + Mass * Vector2.Dot(LocalCenter, LocalCenter);
|
||||
get => _inertia + _mass * Vector2.Dot(_localCenter, _localCenter);
|
||||
set
|
||||
{
|
||||
DebugTools.Assert(!float.IsNaN(value));
|
||||
@@ -600,7 +600,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
if (value > 0.0f && !_fixedRotation)
|
||||
{
|
||||
_inertia = value - Mass * Vector2.Dot(LocalCenter, LocalCenter);
|
||||
_inertia = value - Mass * Vector2.Dot(_localCenter, _localCenter);
|
||||
DebugTools.Assert(_inertia > 0.0f);
|
||||
InvI = 1.0f / _inertia;
|
||||
Dirty();
|
||||
@@ -644,7 +644,6 @@ namespace Robust.Shared.GameObjects
|
||||
[DataField("fixedRotation")]
|
||||
private bool _fixedRotation = true;
|
||||
|
||||
// TODO: Will be used someday
|
||||
/// <summary>
|
||||
/// Get this body's center of mass offset to world position.
|
||||
/// </summary>
|
||||
@@ -658,6 +657,7 @@ namespace Robust.Shared.GameObjects
|
||||
set
|
||||
{
|
||||
if (_bodyType != BodyType.Dynamic) return;
|
||||
|
||||
if (value.EqualsApprox(_localCenter)) return;
|
||||
|
||||
_localCenter = value;
|
||||
@@ -954,6 +954,11 @@ namespace Robust.Shared.GameObjects
|
||||
return Transform.Mul(GetTransform(), localPoint);
|
||||
}
|
||||
|
||||
public Vector2 GetLocalVector2(Vector2 worldVector)
|
||||
{
|
||||
return Transform.MulT(new Quaternion2D((float) Owner.EntityManager.GetComponent<TransformComponent>(OwnerUid).WorldRotation.Theta), worldVector);
|
||||
}
|
||||
|
||||
public void FixtureChanged(Fixture fixture)
|
||||
{
|
||||
// TODO: Optimise this a LOT
|
||||
@@ -989,9 +994,15 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
internal Transform GetTransform()
|
||||
public Transform GetTransform()
|
||||
{
|
||||
return new(Owner.Transform.WorldPosition, (float) Owner.Transform.WorldRotation.Theta);
|
||||
var position = Owner.Transform.WorldPosition;
|
||||
var angle = (float) Owner.Transform.WorldRotation.Theta;
|
||||
|
||||
var xf = new Transform(position, angle);
|
||||
// xf.Position -= Transform.Mul(xf.Quaternion2D, LocalCenter);
|
||||
|
||||
return xf;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1115,8 +1126,7 @@ namespace Robust.Shared.GameObjects
|
||||
_invMass = 0.0f;
|
||||
_inertia = 0.0f;
|
||||
InvI = 0.0f;
|
||||
LocalCenter = Vector2.Zero;
|
||||
// Sweep
|
||||
_localCenter = Vector2.Zero;
|
||||
|
||||
if (((int) _bodyType & (int) BodyType.Kinematic) != 0)
|
||||
{
|
||||
@@ -1124,33 +1134,18 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
|
||||
var localCenter = Vector2.Zero;
|
||||
var shapeManager = IoCManager.Resolve<IShapeManager>();
|
||||
|
||||
foreach (var fixture in _fixtures)
|
||||
{
|
||||
if (fixture.Mass <= 0.0f) continue;
|
||||
|
||||
var fixMass = fixture.Mass;
|
||||
var data = new MassData {Mass = fixture.Mass};
|
||||
shapeManager.GetMassData(fixture.Shape, ref data);
|
||||
|
||||
_mass += fixMass;
|
||||
|
||||
var center = Vector2.Zero;
|
||||
|
||||
// TODO: God this is garbage
|
||||
switch (fixture.Shape)
|
||||
{
|
||||
case PhysShapeAabb aabb:
|
||||
center = aabb.Centroid;
|
||||
break;
|
||||
case PolygonShape poly:
|
||||
center = poly.Centroid;
|
||||
break;
|
||||
case PhysShapeCircle circle:
|
||||
center = circle.Position;
|
||||
break;
|
||||
}
|
||||
|
||||
localCenter += center * fixMass;
|
||||
_inertia += fixture.Inertia;
|
||||
_mass += data.Mass;
|
||||
localCenter += data.Center * data.Mass;
|
||||
_inertia += data.I;
|
||||
}
|
||||
|
||||
if (BodyType == BodyType.Static)
|
||||
@@ -1173,7 +1168,7 @@ namespace Robust.Shared.GameObjects
|
||||
if (_inertia > 0.0f && !_fixedRotation)
|
||||
{
|
||||
// Center inertia about center of mass.
|
||||
_inertia -= _mass * Vector2.Dot(Vector2.Zero, Vector2.Zero);
|
||||
_inertia -= _mass * Vector2.Dot(localCenter, localCenter);
|
||||
|
||||
DebugTools.Assert(_inertia > 0.0f);
|
||||
InvI = 1.0f / _inertia;
|
||||
@@ -1184,16 +1179,19 @@ namespace Robust.Shared.GameObjects
|
||||
InvI = 0.0f;
|
||||
}
|
||||
|
||||
LocalCenter = Vector2.Zero;
|
||||
_localCenter = localCenter;
|
||||
|
||||
// TODO: Calculate Sweep
|
||||
|
||||
/*
|
||||
var oldCenter = _sweep.Center;
|
||||
_sweep.LocalCenter = localCenter;
|
||||
_sweep.Center0 = _sweep.Center = Physics.Transform.Mul(GetTransform(), _sweep.LocalCenter);
|
||||
var oldCenter = Sweep.Center;
|
||||
Sweep.LocalCenter = localCenter;
|
||||
Sweep.Center0 = Sweep.Center = Transform.Mul(GetTransform(), Sweep.LocalCenter);
|
||||
*/
|
||||
|
||||
// Update center of mass velocity.
|
||||
var a = _sweep.Center - oldCenter;
|
||||
_linVelocity += new Vector2(-_angVelocity * a.y, _angVelocity * a.x);
|
||||
*/
|
||||
// _linVelocity += Vector2.Cross(_angVelocity, Worl - oldCenter);
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1218,20 +1216,14 @@ namespace Robust.Shared.GameObjects
|
||||
var aUid = jointComponentA.Owner.Uid;
|
||||
var bUid = jointComponentB.Owner.Uid;
|
||||
|
||||
ValueTuple<EntityUid, EntityUid> uids;
|
||||
|
||||
uids = aUid.CompareTo(bUid) < 0 ?
|
||||
new ValueTuple<EntityUid, EntityUid>(aUid, bUid) :
|
||||
new ValueTuple<EntityUid, EntityUid>(bUid, aUid);
|
||||
|
||||
foreach (var (_, joint) in jointComponentA.Joints)
|
||||
{
|
||||
// Check if either: the joint even allows collisions OR the other body on the joint is actually the other body we're checking.
|
||||
if (joint.CollideConnected ||
|
||||
uids.Item1 != joint.BodyAUid ||
|
||||
uids.Item2 != joint.BodyBUid) continue;
|
||||
|
||||
return false;
|
||||
if (!joint.CollideConnected &&
|
||||
(aUid == joint.BodyAUid &&
|
||||
bUid == joint.BodyBUid) ||
|
||||
(bUid == joint.BodyAUid ||
|
||||
aUid == joint.BodyBUid)) return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -220,6 +220,9 @@ namespace Robust.Shared.GameObjects
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public int EntityCount => Entities.Count;
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IEntity> GetEntities() => Entities.Values;
|
||||
|
||||
|
||||
@@ -80,6 +80,11 @@ namespace Robust.Shared.GameObjects
|
||||
/// <returns>True if a value was returned, false otherwise.</returns>
|
||||
bool TryGetEntity(EntityUid uid, [NotNullWhen(true)] out IEntity? entity);
|
||||
|
||||
/// <summary>
|
||||
/// How many entities are currently active.
|
||||
/// </summary>
|
||||
int EntityCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Returns all entities
|
||||
/// </summary>
|
||||
@@ -91,7 +96,7 @@ namespace Robust.Shared.GameObjects
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
IEnumerable<EntityUid> GetEntityUids();
|
||||
|
||||
|
||||
public void DirtyEntity(EntityUid uid);
|
||||
|
||||
public void QueueDeleteEntity(IEntity entity);
|
||||
|
||||
@@ -54,6 +54,8 @@ namespace Robust.Shared.GameObjects
|
||||
IEnumerable<IEntity> GetEntitiesIntersecting(MapId mapId, Vector2 position, LookupFlags flags = LookupFlags.IncludeAnchored);
|
||||
|
||||
void FastEntitiesIntersecting(in MapId mapId, ref Box2 worldAABB, EntityQueryCallback callback, LookupFlags flags = LookupFlags.IncludeAnchored);
|
||||
|
||||
void FastEntitiesIntersecting(EntityLookupComponent lookup, ref Box2 localAABB, EntityQueryCallback callback);
|
||||
|
||||
IEnumerable<IEntity> GetEntitiesInRange(EntityCoordinates position, float range, LookupFlags flags = LookupFlags.IncludeAnchored);
|
||||
|
||||
@@ -194,7 +196,7 @@ namespace Robust.Shared.GameObjects
|
||||
component.Tree = new DynamicTree<IEntity>(
|
||||
GetRelativeAABBFromEntity,
|
||||
capacity: capacity,
|
||||
growthFunc: x => x == GrowthRate ? GrowthRate * 8 : x + GrowthRate
|
||||
growthFunc: x => x == GrowthRate ? GrowthRate * 8 : x * 2
|
||||
);
|
||||
}
|
||||
|
||||
@@ -395,6 +397,12 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void FastEntitiesIntersecting(EntityLookupComponent lookup, ref Box2 localAABB, EntityQueryCallback callback)
|
||||
{
|
||||
lookup.Tree._b2Tree.FastQuery(ref localAABB, (ref IEntity data) => callback(data));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<IEntity> GetEntitiesIntersecting(MapId mapId, Box2 worldAABB, LookupFlags flags = LookupFlags.IncludeAnchored)
|
||||
{
|
||||
|
||||
@@ -26,6 +26,7 @@ namespace Robust.Shared.GameObjects
|
||||
|
||||
private void RemoveHandler(EntityUid uid, MapGridComponent component, ComponentRemove args)
|
||||
{
|
||||
EntityManager.EventBus.RaiseLocalEvent(uid, new GridRemovalEvent(uid, component.GridIndex));
|
||||
MapManager.OnComponentRemoved(component);
|
||||
}
|
||||
|
||||
@@ -48,6 +49,18 @@ namespace Robust.Shared.GameObjects
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GridRemovalEvent : EntityEventArgs
|
||||
{
|
||||
public EntityUid EntityUid { get; }
|
||||
public GridId GridId { get; }
|
||||
|
||||
public GridRemovalEvent(EntityUid uid, GridId gridId)
|
||||
{
|
||||
EntityUid = uid;
|
||||
GridId = gridId;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Raised whenever a grid is being initialized.
|
||||
/// </summary>
|
||||
|
||||
@@ -18,6 +18,7 @@ namespace Robust.Shared.Network.Messages.Handshake
|
||||
public ImmutableArray<byte> HWId;
|
||||
public bool CanAuth;
|
||||
public bool NeedPubKey;
|
||||
public bool Encrypt;
|
||||
|
||||
public override void ReadFromBuffer(NetIncomingMessage buffer)
|
||||
{
|
||||
@@ -26,6 +27,7 @@ namespace Robust.Shared.Network.Messages.Handshake
|
||||
HWId = ImmutableArray.Create(buffer.ReadBytes(length));
|
||||
CanAuth = buffer.ReadBoolean();
|
||||
NeedPubKey = buffer.ReadBoolean();
|
||||
Encrypt = buffer.ReadBoolean();
|
||||
}
|
||||
|
||||
public override void WriteToBuffer(NetOutgoingMessage buffer)
|
||||
@@ -35,6 +37,7 @@ namespace Robust.Shared.Network.Messages.Handshake
|
||||
buffer.Write(HWId.AsSpan());
|
||||
buffer.Write(CanAuth);
|
||||
buffer.Write(NeedPubKey);
|
||||
buffer.Write(Encrypt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace Robust.Shared.Network.Messages.Handshake
|
||||
buffer.Write(UserData.UserId);
|
||||
buffer.Write(UserData.PatronTier);
|
||||
buffer.Write((byte) Type);
|
||||
buffer.Write(new byte[100]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +122,7 @@ namespace Robust.Shared.Network
|
||||
private async Task CCDoHandshake(NetPeerData peer, NetConnection connection, string userNameRequest,
|
||||
CancellationToken cancel)
|
||||
{
|
||||
var encrypt = _config.GetCVar(CVars.NetEncrypt);
|
||||
var authToken = _authManager.Token;
|
||||
var pubKey = _authManager.PubKey;
|
||||
var authServer = _authManager.Server;
|
||||
@@ -136,7 +137,8 @@ namespace Robust.Shared.Network
|
||||
UserName = userNameRequest,
|
||||
CanAuth = authenticate,
|
||||
NeedPubKey = !hasPubKey,
|
||||
HWId = hwId
|
||||
HWId = hwId,
|
||||
Encrypt = encrypt
|
||||
};
|
||||
|
||||
var outLoginMsg = peer.Peer.CreateMessage();
|
||||
@@ -156,7 +158,8 @@ namespace Robust.Shared.Network
|
||||
var sharedSecret = new byte[AesKeyLength];
|
||||
RandomNumberGenerator.Fill(sharedSecret);
|
||||
|
||||
encryption = new NetAESEncryption(peer.Peer, sharedSecret, 0, sharedSecret.Length);
|
||||
if (encrypt)
|
||||
encryption = new NetAESEncryption(peer.Peer, sharedSecret, 0, sharedSecret.Length);
|
||||
|
||||
byte[] keyBytes;
|
||||
if (hasPubKey)
|
||||
@@ -201,7 +204,7 @@ namespace Robust.Shared.Network
|
||||
|
||||
// Expect login success here.
|
||||
response = await AwaitData(connection, cancel);
|
||||
encryption.Decrypt(response);
|
||||
encryption?.Decrypt(response);
|
||||
}
|
||||
|
||||
var msgSuc = new MsgLoginSuccess();
|
||||
|
||||
@@ -114,7 +114,8 @@ namespace Robust.Shared.Network
|
||||
return;
|
||||
}
|
||||
|
||||
encryption = new NetAESEncryption(peer.Peer, sharedSecret, 0, sharedSecret.Length);
|
||||
if (msgLogin.Encrypt)
|
||||
encryption = new NetAESEncryption(peer.Peer, sharedSecret, 0, sharedSecret.Length);
|
||||
|
||||
var authHashBytes = MakeAuthHash(sharedSecret, RsaPublicKey!);
|
||||
var authHash = Base64Helpers.ConvertToBase64Url(authHashBytes);
|
||||
|
||||
@@ -330,9 +330,6 @@ namespace Robust.Shared.Physics.Collision
|
||||
|
||||
private class EPCollider
|
||||
{
|
||||
private float _polygonRadius;
|
||||
private float _angularSlop;
|
||||
|
||||
private TempPolygon _polygonB;
|
||||
|
||||
Transform _xf;
|
||||
@@ -346,8 +343,6 @@ namespace Robust.Shared.Physics.Collision
|
||||
|
||||
internal EPCollider(IConfigurationManager configManager)
|
||||
{
|
||||
_polygonRadius = PhysicsConstants.PolygonRadius;
|
||||
_angularSlop = configManager.GetCVar(CVars.AngularSlop);
|
||||
_polygonB = new TempPolygon(configManager);
|
||||
}
|
||||
|
||||
@@ -566,7 +561,7 @@ namespace Robust.Shared.Physics.Collision
|
||||
_polygonB.Normals[i] = Transform.Mul(_xf.Quaternion2D, polygonB.Normals[i]);
|
||||
}
|
||||
|
||||
_radius = 2.0f * _polygonRadius;
|
||||
_radius = PhysicsConstants.PolygonRadius;
|
||||
|
||||
manifold.PointCount = 0;
|
||||
|
||||
@@ -802,14 +797,14 @@ namespace Robust.Shared.Physics.Collision
|
||||
// Adjacency
|
||||
if (Vector2.Dot(n, perp) >= 0.0f)
|
||||
{
|
||||
if (Vector2.Dot(n - _upperLimit, _normal) < -_angularSlop)
|
||||
if (Vector2.Dot(n - _upperLimit, _normal) < -PhysicsConstants.AngularSlop)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (Vector2.Dot(n - _lowerLimit, _normal) < -_angularSlop)
|
||||
if (Vector2.Dot(n - _lowerLimit, _normal) < -PhysicsConstants.AngularSlop)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Serialization.Manager.Attributes;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
using Vector2 = Robust.Shared.Maths.Vector2;
|
||||
|
||||
namespace Robust.Shared.Physics.Collision.Shapes
|
||||
{
|
||||
@@ -127,53 +128,11 @@ namespace Robust.Shared.Physics.Collision.Shapes
|
||||
Normals[i] = temp.Normalized;
|
||||
}
|
||||
|
||||
Centroid = ComputeCentroid(Vertices);
|
||||
|
||||
// Compute the polygon mass data
|
||||
// TODO: Update fixture. Maybe use events for it? Who tf knows.
|
||||
// If we get grid polys then we'll actually need runtime updating of bbs.
|
||||
}
|
||||
|
||||
private Vector2 ComputeCentroid(Vector2[] vertices)
|
||||
{
|
||||
var count = vertices.Length;
|
||||
|
||||
DebugTools.Assert(count >= 3);
|
||||
|
||||
var c = new Vector2(0.0f, 0.0f);
|
||||
float area = 0.0f;
|
||||
|
||||
// Get a reference point for forming triangles.
|
||||
// Use the first vertex to reduce round-off errors.
|
||||
var s = vertices[0];
|
||||
|
||||
const float inv3 = 1.0f / 3.0f;
|
||||
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
// Triangle vertices.
|
||||
var p1 = vertices[0] - s;
|
||||
var p2 = vertices[i] - s;
|
||||
var p3 = i + 1 < count ? vertices[i+1] - s : vertices[0] - s;
|
||||
|
||||
var e1 = p2 - p1;
|
||||
var e2 = p3 - p1;
|
||||
|
||||
float D = Vector2.Cross(e1, e2);
|
||||
|
||||
float triangleArea = 0.5f * D;
|
||||
area += triangleArea;
|
||||
|
||||
// Area weighted centroid
|
||||
c += (p1 + p2 + p3) * triangleArea * inv3;
|
||||
}
|
||||
|
||||
// Centroid
|
||||
DebugTools.Assert(area > float.Epsilon);
|
||||
c = c * (1.0f / area) + s;
|
||||
return c;
|
||||
}
|
||||
|
||||
public ShapeType ShapeType => ShapeType.Polygon;
|
||||
|
||||
public PolygonShape()
|
||||
|
||||
@@ -47,10 +47,7 @@ namespace Robust.Shared.Physics
|
||||
GrowthFunc = growthFunc ?? DefaultGrowthFunc;
|
||||
}
|
||||
|
||||
// box2d grows by *2, here we're being somewhat more linear
|
||||
private static int DefaultGrowthFunc(int x)
|
||||
=> x + 256;
|
||||
|
||||
private static int DefaultGrowthFunc(int x) => x * 2;
|
||||
}
|
||||
|
||||
[PublicAPI]
|
||||
|
||||
@@ -35,7 +35,6 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
private bool _warmStarting;
|
||||
private float _velocityThreshold;
|
||||
private float _baumgarte;
|
||||
private float _linearSlop;
|
||||
private float _maxLinearCorrection;
|
||||
private float _maxAngularCorrection;
|
||||
|
||||
@@ -61,7 +60,6 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
_warmStarting = cfg.WarmStarting;
|
||||
_velocityThreshold = cfg.VelocityThreshold;
|
||||
_baumgarte = cfg.Baumgarte;
|
||||
_linearSlop = cfg.LinearSlop;
|
||||
_maxLinearCorrection = cfg.MaxLinearCorrection;
|
||||
_maxAngularCorrection = cfg.MaxAngularCorrection;
|
||||
_positionConstraintsPerThread = cfg.PositionConstraintsPerThread;
|
||||
@@ -163,9 +161,6 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
positionConstraint.IndexA = bodyA.IslandIndex[data.IslandIndex];
|
||||
positionConstraint.IndexB = bodyB.IslandIndex[data.IslandIndex];
|
||||
(positionConstraint.InvMassA, positionConstraint.InvMassB) = (invMassA, invMassB);
|
||||
// TODO: Dis
|
||||
// positionConstraint.LocalCenterA = bodyA._sweep.LocalCenter;
|
||||
// positionConstraint.LocalCenterB = bodyB._sweep.LocalCenter;
|
||||
positionConstraint.LocalCenterA = bodyA.LocalCenter;
|
||||
positionConstraint.LocalCenterB = bodyB.LocalCenter;
|
||||
|
||||
@@ -782,7 +777,7 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
minSeparation = Math.Min(minSeparation, separation);
|
||||
|
||||
// Prevent large corrections and allow slop.
|
||||
float C = Math.Clamp(_baumgarte * (separation + _linearSlop), -_maxLinearCorrection, 0.0f);
|
||||
float C = Math.Clamp(_baumgarte * (separation + PhysicsConstants.LinearSlop), -_maxLinearCorrection, 0.0f);
|
||||
|
||||
// Compute the effective mass.
|
||||
float rnA = Vector2.Cross(rA, normal);
|
||||
@@ -810,7 +805,7 @@ namespace Robust.Shared.Physics.Dynamics.Contacts
|
||||
|
||||
// We can't expect minSpeparation >= -b2_linearSlop because we don't
|
||||
// push the separation above -b2_linearSlop.
|
||||
return minSeparation >= -3.0f * _linearSlop;
|
||||
return minSeparation >= -3.0f * PhysicsConstants.LinearSlop;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -273,6 +273,7 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
fixture._hard = _hard;
|
||||
fixture._collisionLayer = _collisionLayer;
|
||||
fixture._collisionMask = _collisionMask;
|
||||
fixture._mass = _mass;
|
||||
}
|
||||
|
||||
// Moved from Shape because no MassData on Shape anymore (due to serv3 and physics ease-of-use etc etc.)
|
||||
@@ -430,7 +431,8 @@ namespace Robust.Shared.Physics.Dynamics
|
||||
_collisionMask == other.CollisionMask &&
|
||||
Shape.Equals(other.Shape) &&
|
||||
Body == other.Body &&
|
||||
ID.Equals(other.ID);
|
||||
ID.Equals(other.ID) &&
|
||||
MathHelper.CloseTo(_mass, other._mass);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -28,10 +28,8 @@ using System;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
using Robust.Shared.Utility;
|
||||
using Robust.Shared.ViewVariables;
|
||||
|
||||
namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
@@ -62,10 +60,6 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
LocalAnchorB = LocalAnchorB
|
||||
};
|
||||
|
||||
var configManager = IoCManager.Resolve<IConfigurationManager>();
|
||||
joint.LinearSlop = configManager.GetCVar(CVars.LinearSlop);
|
||||
joint.WarmStarting = configManager.GetCVar(CVars.WarmStarting);
|
||||
|
||||
return joint;
|
||||
}
|
||||
|
||||
@@ -119,9 +113,6 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
private float _currentLength;
|
||||
private float _softMass;
|
||||
|
||||
internal float LinearSlop;
|
||||
internal bool WarmStarting;
|
||||
|
||||
public override JointType JointType => JointType.Distance;
|
||||
|
||||
/// <summary>
|
||||
@@ -139,9 +130,11 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
public DistanceJoint(EntityUid bodyA, EntityUid bodyB, Vector2 anchorA, Vector2 anchorB)
|
||||
: base(bodyA, bodyB)
|
||||
{
|
||||
Length = MathF.Max(LinearSlop, (BodyB.GetWorldPoint(anchorB) - BodyA.GetWorldPoint(anchorA)).Length);
|
||||
Length = MathF.Max(PhysicsConstants.LinearSlop, (BodyB.GetWorldPoint(anchorB) - BodyA.GetWorldPoint(anchorA)).Length);
|
||||
_minLength = _length;
|
||||
_maxLength = _length;
|
||||
LocalAnchorA = anchorA;
|
||||
LocalAnchorB = anchorB;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -157,7 +150,7 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
if (MathHelper.CloseTo(value, _length)) return;
|
||||
|
||||
_impulse = 0.0f;
|
||||
_length = MathF.Max(value, LinearSlop);
|
||||
_length = MathF.Max(value, PhysicsConstants.LinearSlop);
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
@@ -195,13 +188,16 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
if (MathHelper.CloseTo(value, _minLength)) return;
|
||||
|
||||
_lowerImpulse = 0.0f;
|
||||
_minLength = Math.Clamp(value, LinearSlop, MaxLength);
|
||||
_minLength = Math.Clamp(value, PhysicsConstants.LinearSlop, MaxLength);
|
||||
Dirty();
|
||||
}
|
||||
}
|
||||
|
||||
private float _minLength;
|
||||
|
||||
/// <summary>
|
||||
/// The linear stiffness in N/m.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Stiffness
|
||||
{
|
||||
@@ -217,6 +213,9 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
|
||||
private float _stiffness;
|
||||
|
||||
/// <summary>
|
||||
/// The linear damping in N*s/m.
|
||||
/// </summary>
|
||||
[ViewVariables(VVAccess.ReadWrite)]
|
||||
public float Damping
|
||||
{
|
||||
@@ -287,8 +286,8 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
{
|
||||
_indexA = BodyA.IslandIndex[data.IslandIndex];
|
||||
_indexB = BodyB.IslandIndex[data.IslandIndex];
|
||||
_localCenterA = Vector2.Zero; //BodyA->m_sweep.localCenter;
|
||||
_localCenterB = Vector2.Zero; //BodyB->m_sweep.localCenter;
|
||||
_localCenterA = BodyA.LocalCenter;
|
||||
_localCenterB = BodyB.LocalCenter;
|
||||
_invMassA = BodyA.InvMass;
|
||||
_invMassB = BodyB.InvMass;
|
||||
_invIA = BodyA.InvI;
|
||||
@@ -312,7 +311,7 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
|
||||
// Handle singularity.
|
||||
_currentLength = _u.Length;
|
||||
if (_currentLength > LinearSlop)
|
||||
if (_currentLength > data.LinearSlop)
|
||||
{
|
||||
_u *= 1.0f / _currentLength;
|
||||
}
|
||||
@@ -358,7 +357,7 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
_softMass = _mass;
|
||||
}
|
||||
|
||||
if (WarmStarting)
|
||||
if (data.WarmStarting)
|
||||
{
|
||||
// Scale the impulse to support a variable time step.
|
||||
_impulse *= data.DtRatio;
|
||||
@@ -523,7 +522,7 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
data.Positions[_indexB] = cB;
|
||||
data.Angles[_indexB] = aB;
|
||||
|
||||
return MathF.Abs(C) < LinearSlop;
|
||||
return MathF.Abs(C) < data.LinearSlop;
|
||||
}
|
||||
|
||||
public bool Equals(DistanceJoint? other)
|
||||
|
||||
@@ -173,8 +173,8 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
{
|
||||
_indexA = BodyA.IslandIndex[data.IslandIndex];
|
||||
_indexB = BodyB.IslandIndex[data.IslandIndex];
|
||||
_localCenterA = Vector2.Zero; // BodyA._sweep.LocalCenter;
|
||||
_localCenterB = Vector2.Zero; //BodyB._sweep.LocalCenter;
|
||||
_localCenterA = BodyA.LocalCenter;
|
||||
_localCenterB = BodyB.LocalCenter;
|
||||
_invMassA = BodyA.InvMass;
|
||||
_invMassB = BodyB.InvMass;
|
||||
_invIA = BodyA.InvI;
|
||||
|
||||
@@ -208,19 +208,8 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
|
||||
protected Joint(EntityUid bodyAUid, EntityUid bodyBUid)
|
||||
{
|
||||
var uidA = bodyAUid;
|
||||
var uidB = bodyBUid;
|
||||
|
||||
if (uidA.CompareTo(uidB) >= 0)
|
||||
{
|
||||
BodyAUid = uidB;
|
||||
BodyBUid = uidA;
|
||||
}
|
||||
else
|
||||
{
|
||||
BodyAUid = uidA;
|
||||
BodyBUid = uidB;
|
||||
}
|
||||
BodyAUid = bodyAUid;
|
||||
BodyBUid = bodyBUid;
|
||||
|
||||
//Can't connect a joint to the same body twice.
|
||||
Debug.Assert(BodyAUid != BodyBUid);
|
||||
|
||||
583
Robust.Shared/Physics/Dynamics/Joints/PrismaticJoint.cs
Normal file
583
Robust.Shared/Physics/Dynamics/Joints/PrismaticJoint.cs
Normal file
@@ -0,0 +1,583 @@
|
||||
// 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.
|
||||
|
||||
using System;
|
||||
using Robust.Shared.Configuration;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
{
|
||||
// Linear constraint (point-to-line)
|
||||
// d = p2 - p1 = x2 + r2 - x1 - r1
|
||||
// C = dot(perp, d)
|
||||
// Cdot = dot(d, cross(w1, perp)) + dot(perp, v2 + cross(w2, r2) - v1 - cross(w1, r1))
|
||||
// = -dot(perp, v1) - dot(cross(d + r1, perp), w1) + dot(perp, v2) + dot(cross(r2, perp), v2)
|
||||
// J = [-perp, -cross(d + r1, perp), perp, cross(r2,perp)]
|
||||
//
|
||||
// Angular constraint
|
||||
// C = a2 - a1 + a_initial
|
||||
// Cdot = w2 - w1
|
||||
// J = [0 0 -1 0 0 1]
|
||||
//
|
||||
// K = J * invM * JT
|
||||
//
|
||||
// J = [-a -s1 a s2]
|
||||
// [0 -1 0 1]
|
||||
// a = perp
|
||||
// s1 = cross(d + r1, a) = cross(p2 - x1, a)
|
||||
// s2 = cross(r2, a) = cross(p2 - x2, a)
|
||||
|
||||
// Motor/Limit linear constraint
|
||||
// C = dot(ax1, d)
|
||||
// Cdot = -dot(ax1, v1) - dot(cross(d + r1, ax1), w1) + dot(ax1, v2) + dot(cross(r2, ax1), v2)
|
||||
// J = [-ax1 -cross(d+r1,ax1) ax1 cross(r2,ax1)]
|
||||
|
||||
// Predictive limit is applied even when the limit is not active.
|
||||
// Prevents a constraint speed that can lead to a constraint error in one time step.
|
||||
// Want C2 = C1 + h * Cdot >= 0
|
||||
// Or:
|
||||
// Cdot + C1/h >= 0
|
||||
// I do not apply a negative constraint error because that is handled in position correction.
|
||||
// So:
|
||||
// Cdot + max(C1, 0)/h >= 0
|
||||
|
||||
// Block Solver
|
||||
// We develop a block solver that includes the angular and linear constraints. This makes the limit stiffer.
|
||||
//
|
||||
// The Jacobian has 2 rows:
|
||||
// J = [-uT -s1 uT s2] // linear
|
||||
// [0 -1 0 1] // angular
|
||||
//
|
||||
// u = perp
|
||||
// s1 = cross(d + r1, u), s2 = cross(r2, u)
|
||||
// a1 = cross(d + r1, v), a2 = cross(r2, v)
|
||||
|
||||
[Serializable, NetSerializable]
|
||||
internal sealed class PrismaticJointState : JointState
|
||||
{
|
||||
/// The local translation unit axis in bodyA.
|
||||
public Vector2 LocalAxisA;
|
||||
|
||||
/// The constrained angle between the bodies: bodyB_angle - bodyA_angle.
|
||||
public float ReferenceAngle;
|
||||
|
||||
/// Enable/disable the joint limit.
|
||||
public bool EnableLimit;
|
||||
|
||||
/// The lower translation limit, usually in meters.
|
||||
public float LowerTranslation;
|
||||
|
||||
/// The upper translation limit, usually in meters.
|
||||
public float UpperTranslation;
|
||||
|
||||
/// Enable/disable the joint motor.
|
||||
public bool EnableMotor;
|
||||
|
||||
/// The maximum motor torque, usually in N-m.
|
||||
public float MaxMotorForce;
|
||||
|
||||
/// The desired motor speed in radians per second.
|
||||
public float MotorSpeed;
|
||||
|
||||
public override Joint GetJoint()
|
||||
{
|
||||
var joint = new PrismaticJoint(UidA, UidB)
|
||||
{
|
||||
ID = ID,
|
||||
Breakpoint = Breakpoint,
|
||||
CollideConnected = CollideConnected,
|
||||
Enabled = Enabled,
|
||||
LocalAxisA = LocalAxisA,
|
||||
ReferenceAngle = ReferenceAngle,
|
||||
EnableLimit = EnableLimit,
|
||||
LowerTranslation = LowerTranslation,
|
||||
UpperTranslation = UpperTranslation,
|
||||
EnableMotor = EnableMotor,
|
||||
MaxMotorForce = MaxMotorForce,
|
||||
MotorSpeed = MotorSpeed,
|
||||
LocalAnchorA = LocalAnchorA,
|
||||
LocalAnchorB = LocalAnchorB
|
||||
};
|
||||
|
||||
return joint;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Prismatic joint definition. This requires defining a line of
|
||||
/// motion using an axis and an anchor point. The definition uses local
|
||||
/// anchor points and a local axis so that the initial configuration
|
||||
/// can violate the constraint slightly. The joint translation is zero
|
||||
/// when the local anchor points coincide in world space.
|
||||
/// </summary>
|
||||
public sealed class PrismaticJoint : Joint, IEquatable<PrismaticJoint>
|
||||
{
|
||||
/// The local translation unit axis in bodyA.
|
||||
public Vector2 LocalAxisA
|
||||
{
|
||||
get => _localAxisA;
|
||||
set
|
||||
{
|
||||
_localAxisA = value;
|
||||
_localXAxisA = value.Normalized;
|
||||
_localYAxisA = Vector2.Cross(1f, _localXAxisA);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector2 _localAxisA;
|
||||
|
||||
/// The constrained angle between the bodies: bodyB_angle - bodyA_angle.
|
||||
public float ReferenceAngle;
|
||||
|
||||
/// Enable/disable the joint limit.
|
||||
public bool EnableLimit;
|
||||
|
||||
/// The lower translation limit, usually in meters.
|
||||
public float LowerTranslation;
|
||||
|
||||
/// The upper translation limit, usually in meters.
|
||||
public float UpperTranslation;
|
||||
|
||||
/// Enable/disable the joint motor.
|
||||
public bool EnableMotor;
|
||||
|
||||
/// The maximum motor torque, usually in N-m.
|
||||
public float MaxMotorForce;
|
||||
|
||||
/// The desired motor speed in radians per second.
|
||||
public float MotorSpeed;
|
||||
|
||||
internal Vector2 _localXAxisA;
|
||||
internal Vector2 _localYAxisA;
|
||||
|
||||
private Vector2 _impulse;
|
||||
private float _motorImpulse;
|
||||
private float _lowerImpulse;
|
||||
private float _upperImpulse;
|
||||
|
||||
// Solver temp
|
||||
private int _indexA;
|
||||
private int _indexB;
|
||||
private Vector2 _localCenterA;
|
||||
private Vector2 _localCenterB;
|
||||
private float _invMassA;
|
||||
private float _invMassB;
|
||||
private float _invIA;
|
||||
private float _invIB;
|
||||
private Vector2 _axis, _perp;
|
||||
private float _s1, _s2;
|
||||
private float _a1, _a2;
|
||||
Matrix22 _K;
|
||||
private float _translation;
|
||||
private float _axialMass;
|
||||
|
||||
public PrismaticJoint(EntityUid bodyAUid, EntityUid bodyBUid) : base(bodyAUid, bodyBUid)
|
||||
{
|
||||
LocalAxisA = new Vector2(1f, 0f);
|
||||
}
|
||||
|
||||
public PrismaticJoint(EntityUid bodyAUid, EntityUid bodyBUid, Vector2 anchor, Vector2 axis, IEntityManager entityManager) : base(bodyAUid, bodyBUid)
|
||||
{
|
||||
var xformA = entityManager.GetComponent<TransformComponent>(bodyAUid);
|
||||
var xformB = entityManager.GetComponent<TransformComponent>(bodyBUid);
|
||||
|
||||
LocalAnchorA = xformA.InvWorldMatrix.Transform(anchor);
|
||||
LocalAnchorB = xformB.InvWorldMatrix.Transform(anchor);
|
||||
LocalAxisA = entityManager.GetComponent<PhysicsComponent>(bodyAUid).GetLocalVector2(axis);
|
||||
ReferenceAngle = (float) (xformB.WorldRotation - xformA.WorldRotation);
|
||||
}
|
||||
|
||||
public override JointType JointType => JointType.Prismatic;
|
||||
|
||||
public override JointState GetState()
|
||||
{
|
||||
var prismaticState = new PrismaticJointState
|
||||
{
|
||||
LocalAnchorA = LocalAnchorA,
|
||||
LocalAnchorB = LocalAnchorB
|
||||
};
|
||||
|
||||
base.GetState(prismaticState);
|
||||
return prismaticState;
|
||||
}
|
||||
|
||||
public override Vector2 GetReactionForce(float invDt)
|
||||
{
|
||||
return (_perp * _impulse.X + _axis * (_motorImpulse + _lowerImpulse - _upperImpulse)) * invDt;
|
||||
}
|
||||
|
||||
public override float GetReactionTorque(float invDt)
|
||||
{
|
||||
return invDt * _impulse.Y;
|
||||
}
|
||||
|
||||
internal override void InitVelocityConstraints(SolverData data)
|
||||
{
|
||||
_indexA = BodyA.IslandIndex[data.IslandIndex];
|
||||
_indexB = BodyB.IslandIndex[data.IslandIndex];
|
||||
_localCenterA = BodyA.LocalCenter;
|
||||
_localCenterB = BodyB.LocalCenter;
|
||||
_invMassA = BodyA.InvMass;
|
||||
_invMassB = BodyB.InvMass;
|
||||
_invIA = BodyA.InvI;
|
||||
_invIB = BodyB.InvI;
|
||||
|
||||
var cA = data.Positions[_indexA];
|
||||
float aA = data.Angles[_indexA];
|
||||
var vA = data.Positions[_indexA];
|
||||
float wA = data.Angles[_indexA];
|
||||
|
||||
var cB = data.Positions[_indexB];
|
||||
float aB = data.Angles[_indexB];
|
||||
var vB = data.Positions[_indexB];
|
||||
float wB = data.Angles[_indexB];
|
||||
|
||||
Quaternion2D qA = new(aA), qB = new(aB);
|
||||
|
||||
// Compute the effective masses.
|
||||
var rA = Transform.Mul(qA, LocalAnchorA - _localCenterA);
|
||||
var rB = Transform.Mul(qB, LocalAnchorB - _localCenterB);
|
||||
var d = (cB - cA) + rB - rA;
|
||||
|
||||
float mA = _invMassA, mB = _invMassB;
|
||||
float iA = _invIA, iB = _invIB;
|
||||
|
||||
// Compute motor Jacobian and effective mass.
|
||||
{
|
||||
_axis = Transform.Mul(qA, _localXAxisA);
|
||||
_a1 = Vector2.Cross(d + rA, _axis);
|
||||
_a2 = Vector2.Cross(rB, _axis);
|
||||
|
||||
_axialMass = mA + mB + iA * _a1 * _a1 + iB * _a2 * _a2;
|
||||
if (_axialMass > 0.0f)
|
||||
{
|
||||
_axialMass = 1.0f / _axialMass;
|
||||
}
|
||||
}
|
||||
|
||||
// Prismatic constraint.
|
||||
{
|
||||
_perp = Transform.Mul(qA, _localYAxisA);
|
||||
|
||||
_s1 = Vector2.Cross(d + rA, _perp);
|
||||
_s2 = Vector2.Cross(rB, _perp);
|
||||
|
||||
float k11 = mA + mB + iA * _s1 * _s1 + iB * _s2 * _s2;
|
||||
float k12 = iA * _s1 + iB * _s2;
|
||||
float k22 = iA + iB;
|
||||
if (k22 == 0.0f)
|
||||
{
|
||||
// For bodies with fixed rotation.
|
||||
k22 = 1.0f;
|
||||
}
|
||||
|
||||
_K = new Matrix22(new Vector2(k11, k12), new Vector2(k12, k22));
|
||||
}
|
||||
|
||||
if (EnableLimit)
|
||||
{
|
||||
_translation = Vector2.Dot(_axis, d);
|
||||
}
|
||||
else
|
||||
{
|
||||
_lowerImpulse = 0.0f;
|
||||
_upperImpulse = 0.0f;
|
||||
}
|
||||
|
||||
if (!EnableMotor)
|
||||
{
|
||||
_motorImpulse = 0.0f;
|
||||
}
|
||||
|
||||
if (data.WarmStarting)
|
||||
{
|
||||
// Account for variable time step.
|
||||
_impulse *= data.DtRatio;
|
||||
_motorImpulse *= data.DtRatio;
|
||||
_lowerImpulse *= data.DtRatio;
|
||||
_upperImpulse *= data.DtRatio;
|
||||
|
||||
float axialImpulse = _motorImpulse + _lowerImpulse - _upperImpulse;
|
||||
Vector2 P = _perp * _impulse.X + _axis * axialImpulse;
|
||||
float LA = _impulse.X * _s1 + _impulse.Y + axialImpulse * _a1;
|
||||
float LB = _impulse.X * _s2 + _impulse.Y + axialImpulse * _a2;
|
||||
|
||||
vA -= P * mA;
|
||||
wA -= iA * LA;
|
||||
|
||||
vB += P * mB;
|
||||
wB += iB * LB;
|
||||
}
|
||||
else
|
||||
{
|
||||
_impulse = Vector2.Zero;
|
||||
_motorImpulse = 0.0f;
|
||||
_lowerImpulse = 0.0f;
|
||||
_upperImpulse = 0.0f;
|
||||
}
|
||||
|
||||
data.LinearVelocities[_indexA] = vA;
|
||||
data.AngularVelocities[_indexA] = wA;
|
||||
data.LinearVelocities[_indexB] = vB;
|
||||
data.AngularVelocities[_indexB] = wB;
|
||||
}
|
||||
|
||||
internal override void SolveVelocityConstraints(SolverData data)
|
||||
{
|
||||
Vector2 vA = data.LinearVelocities[_indexA];
|
||||
float wA = data.AngularVelocities[_indexA];
|
||||
Vector2 vB = data.LinearVelocities[_indexB];
|
||||
float wB = data.AngularVelocities[_indexB];
|
||||
|
||||
float mA = _invMassA, mB = _invMassB;
|
||||
float iA = _invIA, iB = _invIB;
|
||||
|
||||
// Solve linear motor constraint
|
||||
if (EnableMotor)
|
||||
{
|
||||
float Cdot = Vector2.Dot(_axis, vB - vA) + _a2 * wB - _a1 * wA;
|
||||
float impulse = _axialMass * (MotorSpeed - Cdot);
|
||||
float oldImpulse = _motorImpulse;
|
||||
float maxImpulse = data.FrameTime * MaxMotorForce;
|
||||
_motorImpulse = Math.Clamp(_motorImpulse + impulse, -maxImpulse, maxImpulse);
|
||||
impulse = _motorImpulse - oldImpulse;
|
||||
|
||||
Vector2 P = _axis * impulse;
|
||||
float LA = impulse * _a1;
|
||||
float LB = impulse * _a2;
|
||||
|
||||
vA -= P * mA;
|
||||
wA -= iA * LA;
|
||||
vB += P * mB;
|
||||
wB += iB * LB;
|
||||
}
|
||||
|
||||
if (EnableLimit)
|
||||
{
|
||||
// Lower limit
|
||||
{
|
||||
float C = _translation - LowerTranslation;
|
||||
float Cdot = Vector2.Dot(_axis, vB - vA) + _a2 * wB - _a1 * wA;
|
||||
float impulse = -_axialMass * (Cdot + MathF.Max(C, 0.0f) * data.InvDt);
|
||||
float oldImpulse = _lowerImpulse;
|
||||
_lowerImpulse = MathF.Max(_lowerImpulse + impulse, 0.0f);
|
||||
impulse = _lowerImpulse - oldImpulse;
|
||||
|
||||
Vector2 P = _axis * impulse;
|
||||
float LA = impulse * _a1;
|
||||
float LB = impulse * _a2;
|
||||
|
||||
vA -= P * mA;
|
||||
wA -= iA * LA;
|
||||
vB += P * mB;
|
||||
wB += iB * LB;
|
||||
}
|
||||
|
||||
// Upper limit
|
||||
// Note: signs are flipped to keep C positive when the constraint is satisfied.
|
||||
// This also keeps the impulse positive when the limit is active.
|
||||
{
|
||||
float C = UpperTranslation - _translation;
|
||||
float Cdot = Vector2.Dot(_axis, vA - vB) + _a1 * wA - _a2 * wB;
|
||||
float impulse = -_axialMass * (Cdot + MathF.Max(C, 0.0f) * data.InvDt);
|
||||
float oldImpulse = _upperImpulse;
|
||||
_upperImpulse = MathF.Max(_upperImpulse + impulse, 0.0f);
|
||||
impulse = _upperImpulse - oldImpulse;
|
||||
|
||||
Vector2 P = _axis * impulse;
|
||||
float LA = impulse * _a1;
|
||||
float LB = impulse * _a2;
|
||||
|
||||
vA += P * mA;
|
||||
wA += iA * LA;
|
||||
vB -= P * mB;
|
||||
wB -= iB * LB;
|
||||
}
|
||||
}
|
||||
|
||||
// Solve the prismatic constraint in block form.
|
||||
{
|
||||
Vector2 Cdot;
|
||||
Cdot.X = Vector2.Dot(_perp, vB - vA) + _s2 * wB - _s1 * wA;
|
||||
Cdot.Y = wB - wA;
|
||||
|
||||
Vector2 df = _K.Solve(-Cdot);
|
||||
_impulse += df;
|
||||
|
||||
Vector2 P = _perp * df.X;
|
||||
float LA = df.X * _s1 + df.Y;
|
||||
float LB = df.X * _s2 + df.Y;
|
||||
|
||||
vA -= P * mA;
|
||||
wA -= iA * LA;
|
||||
|
||||
vB += P * mB;
|
||||
wB += iB * LB;
|
||||
}
|
||||
|
||||
data.LinearVelocities[_indexA] = vA;
|
||||
data.AngularVelocities[_indexA] = wA;
|
||||
data.LinearVelocities[_indexB] = vB;
|
||||
data.AngularVelocities[_indexB] = wB;
|
||||
}
|
||||
|
||||
internal override bool SolvePositionConstraints(SolverData data)
|
||||
{
|
||||
Vector2 cA = data.Positions[_indexA];
|
||||
float aA = data.Angles[_indexA];
|
||||
Vector2 cB = data.Positions[_indexB];
|
||||
float aB = data.Angles[_indexB];
|
||||
|
||||
Quaternion2D qA = new(aA), qB = new(aB);
|
||||
|
||||
float mA = _invMassA, mB = _invMassB;
|
||||
float iA = _invIA, iB = _invIB;
|
||||
|
||||
// Compute fresh Jacobians
|
||||
Vector2 rA = Transform.Mul(qA, LocalAnchorA - _localCenterA);
|
||||
Vector2 rB = Transform.Mul(qB, LocalAnchorB - _localCenterB);
|
||||
Vector2 d = cB + rB - cA - rA;
|
||||
|
||||
Vector2 axis = Transform.Mul(qA, _localXAxisA);
|
||||
float a1 = Vector2.Cross(d + rA, axis);
|
||||
float a2 = Vector2.Cross(rB, axis);
|
||||
Vector2 perp = Transform.Mul(qA, _localYAxisA);
|
||||
|
||||
float s1 = Vector2.Cross(d + rA, perp);
|
||||
float s2 = Vector2.Cross(rB, perp);
|
||||
|
||||
Vector3 impulse;
|
||||
Vector2 C1;
|
||||
C1.X = Vector2.Dot(perp, d);
|
||||
C1.Y = aB - aA - ReferenceAngle;
|
||||
|
||||
float linearError = MathF.Abs(C1.X);
|
||||
float angularError = MathF.Abs(C1.Y);
|
||||
|
||||
bool active = false;
|
||||
float C2 = 0.0f;
|
||||
if (EnableLimit)
|
||||
{
|
||||
float translation = Vector2.Dot(axis, d);
|
||||
if (MathF.Abs(UpperTranslation - LowerTranslation) < 2.0f * data.LinearSlop)
|
||||
{
|
||||
C2 = translation;
|
||||
linearError = MathF.Max(linearError, MathF.Abs(translation));
|
||||
active = true;
|
||||
}
|
||||
else if (translation <= LowerTranslation)
|
||||
{
|
||||
C2 = MathF.Min(translation - LowerTranslation, 0.0f);
|
||||
linearError = MathF.Max(linearError, LowerTranslation - translation);
|
||||
active = true;
|
||||
}
|
||||
else if (translation >= UpperTranslation)
|
||||
{
|
||||
C2 = MathF.Max(translation - UpperTranslation, 0.0f);
|
||||
linearError = MathF.Max(linearError, translation - UpperTranslation);
|
||||
active = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (active)
|
||||
{
|
||||
float k11 = mA + mB + iA * s1 * s1 + iB * s2 * s2;
|
||||
float k12 = iA * s1 + iB * s2;
|
||||
float k13 = iA * s1 * a1 + iB * s2 * a2;
|
||||
float k22 = iA + iB;
|
||||
if (k22 == 0.0f)
|
||||
{
|
||||
// For fixed rotation
|
||||
k22 = 1.0f;
|
||||
}
|
||||
float k23 = iA * a1 + iB * a2;
|
||||
float k33 = mA + mB + iA * a1 * a1 + iB * a2 * a2;
|
||||
|
||||
Matrix33 K = new Matrix33(
|
||||
new Vector3(k11, k12, k13),
|
||||
new Vector3(k12, k22, k23),
|
||||
new Vector3(k13, k23, k33));
|
||||
|
||||
Vector3 C;
|
||||
C.X = C1.X;
|
||||
C.Y = C1.Y;
|
||||
C.Z = C2;
|
||||
|
||||
impulse = K.Solve33(-C);
|
||||
}
|
||||
else
|
||||
{
|
||||
float k11 = mA + mB + iA * s1 * s1 + iB * s2 * s2;
|
||||
float k12 = iA * s1 + iB * s2;
|
||||
float k22 = iA + iB;
|
||||
if (k22 == 0.0f)
|
||||
{
|
||||
k22 = 1.0f;
|
||||
}
|
||||
|
||||
Matrix22 K;
|
||||
K = new Matrix22(k11, k12, k12, k22);
|
||||
|
||||
Vector2 impulse1 = K.Solve(-C1);
|
||||
impulse.X = impulse1.X;
|
||||
impulse.Y = impulse1.Y;
|
||||
impulse.Z = 0.0f;
|
||||
}
|
||||
|
||||
Vector2 P = perp * impulse.X + axis * impulse.Z;
|
||||
float LA = impulse.X * s1 + impulse.Y + impulse.Z * a1;
|
||||
float LB = impulse.X * s2 + impulse.Y + impulse.Z * a2;
|
||||
|
||||
cA -= P * mA;
|
||||
aA -= iA * LA;
|
||||
cB += P * mB;
|
||||
aB += iB * LB;
|
||||
|
||||
data.Positions[_indexA] = cA;
|
||||
data.Angles[_indexA] = aA;
|
||||
data.Positions[_indexB] = cB;
|
||||
data.Angles[_indexB] = aB;
|
||||
|
||||
return linearError <= data.LinearSlop && angularError <= data.AngularSlop;
|
||||
}
|
||||
|
||||
public bool Equals(PrismaticJoint? other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
if (!base.Equals(other)) return false;
|
||||
|
||||
return EnableLimit.Equals(other.EnableLimit) &&
|
||||
EnableMotor.Equals(other.EnableMotor) &&
|
||||
LocalAxisA.EqualsApprox(other.LocalAxisA) &&
|
||||
MathHelper.CloseTo(ReferenceAngle, other.ReferenceAngle) &&
|
||||
MathHelper.CloseTo(LowerTranslation, other.LowerTranslation) &&
|
||||
MathHelper.CloseTo(UpperTranslation, other.UpperTranslation) &&
|
||||
MathHelper.CloseTo(MaxMotorForce, other.MaxMotorForce) &&
|
||||
MathHelper.CloseTo(MotorSpeed, other.MotorSpeed);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,6 @@
|
||||
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
@@ -60,7 +59,6 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
|
||||
return joint;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public class RevoluteJoint : Joint, IEquatable<RevoluteJoint>
|
||||
@@ -86,11 +84,36 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
|
||||
// Settable
|
||||
public bool EnableLimit;
|
||||
|
||||
/// <summary>
|
||||
/// A flag to enable the joint motor.
|
||||
/// </summary>
|
||||
public bool EnableMotor;
|
||||
|
||||
/// <summary>
|
||||
/// The bodyB angle minus bodyA angle in the reference state (radians).
|
||||
/// </summary>
|
||||
public float ReferenceAngle;
|
||||
|
||||
/// <summary>
|
||||
/// The lower angle for the joint limit (radians).
|
||||
/// </summary>
|
||||
public float LowerAngle;
|
||||
|
||||
/// <summary>
|
||||
/// The upper angle for the joint limit (radians).
|
||||
/// </summary>
|
||||
public float UpperAngle;
|
||||
|
||||
/// <summary>
|
||||
/// The desired motor speed. Usually in radians per second.
|
||||
/// </summary>
|
||||
public float MotorSpeed;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum motor torque used to achieve the desired motor speed.
|
||||
/// Usually in N-m.
|
||||
/// </summary>
|
||||
public float MaxMotorTorque;
|
||||
|
||||
public RevoluteJoint(PhysicsComponent bodyA, PhysicsComponent bodyB, Vector2 anchor) : base(bodyA.Owner.Uid, bodyB.Owner.Uid)
|
||||
@@ -141,8 +164,8 @@ namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
{
|
||||
_indexA = BodyA.IslandIndex[data.IslandIndex];
|
||||
_indexB = BodyB.IslandIndex[data.IslandIndex];
|
||||
_localCenterA = Vector2.Zero; //BodyA->m_sweep.localCenter;
|
||||
_localCenterB = Vector2.Zero; //BodyB->m_sweep.localCenter;
|
||||
_localCenterA = BodyA.LocalCenter;
|
||||
_localCenterB = BodyB.LocalCenter;
|
||||
_invMassA = BodyA.InvMass;
|
||||
_invMassB = BodyB.InvMass;
|
||||
_invIA = BodyA.InvI;
|
||||
|
||||
349
Robust.Shared/Physics/Dynamics/Joints/WeldJoint.cs
Normal file
349
Robust.Shared/Physics/Dynamics/Joints/WeldJoint.cs
Normal file
@@ -0,0 +1,349 @@
|
||||
using System;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Serialization;
|
||||
|
||||
namespace Robust.Shared.Physics.Dynamics.Joints
|
||||
{
|
||||
[Serializable, NetSerializable]
|
||||
internal sealed class WeldJointState : JointState
|
||||
{
|
||||
public float Stiffness { get; internal set; }
|
||||
public float Damping { get; internal set; }
|
||||
public float Bias { get; internal set; }
|
||||
|
||||
public override Joint GetJoint()
|
||||
{
|
||||
var joint = new WeldJoint(UidA, UidB)
|
||||
{
|
||||
ID = ID,
|
||||
Breakpoint = Breakpoint,
|
||||
CollideConnected = CollideConnected,
|
||||
Enabled = Enabled,
|
||||
LocalAnchorA = LocalAnchorA,
|
||||
LocalAnchorB = LocalAnchorB,
|
||||
Stiffness = Stiffness,
|
||||
Damping = Damping,
|
||||
Bias = Bias,
|
||||
};
|
||||
|
||||
return joint;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class WeldJoint : Joint, IEquatable<WeldJoint>
|
||||
{
|
||||
public float Stiffness;
|
||||
public float Damping;
|
||||
public float Bias;
|
||||
|
||||
// Shared
|
||||
private float _gamma;
|
||||
private Vector3 _impulse;
|
||||
|
||||
// Temporary
|
||||
private int _indexA;
|
||||
private int _indexB;
|
||||
private Vector2 _rA;
|
||||
private Vector2 _rB;
|
||||
private Vector2 _localCenterA;
|
||||
private Vector2 _localCenterB;
|
||||
private float _invMassA;
|
||||
private float _invMassB;
|
||||
private float _invIA;
|
||||
private float _invIB;
|
||||
private Matrix33 _mass;
|
||||
|
||||
// Settable
|
||||
/// <summary>
|
||||
/// The bodyB angle minus bodyA angle in the reference state (radians).
|
||||
/// </summary>
|
||||
public float ReferenceAngle;
|
||||
|
||||
public WeldJoint(PhysicsComponent bodyA, PhysicsComponent bodyB, Vector2 anchor) : base(bodyA.Owner.Uid, bodyB.Owner.Uid)
|
||||
{
|
||||
LocalAnchorA = bodyA.GetLocalPoint(anchor);
|
||||
LocalAnchorB = bodyB.GetLocalPoint(anchor);
|
||||
ReferenceAngle = (float) (bodyB.Owner.Transform.WorldRotation - bodyA.Owner.Transform.WorldRotation).Theta;
|
||||
}
|
||||
|
||||
public WeldJoint(EntityUid bodyAUid, EntityUid bodyBUid) : base(bodyAUid, bodyBUid) {}
|
||||
|
||||
public override JointType JointType => JointType.Weld;
|
||||
public override JointState GetState()
|
||||
{
|
||||
var weldJointState = new WeldJointState();
|
||||
|
||||
base.GetState(weldJointState);
|
||||
return weldJointState;
|
||||
}
|
||||
|
||||
public override Vector2 GetReactionForce(float invDt)
|
||||
{
|
||||
var P = new Vector2(_impulse.X, _impulse.Y);
|
||||
return P * invDt;
|
||||
}
|
||||
|
||||
public override float GetReactionTorque(float invDt)
|
||||
{
|
||||
return invDt * _impulse.Z;
|
||||
}
|
||||
|
||||
internal override void InitVelocityConstraints(SolverData data)
|
||||
{
|
||||
_indexA = BodyA.IslandIndex[data.IslandIndex];
|
||||
_indexB = BodyB.IslandIndex[data.IslandIndex];
|
||||
_localCenterA = BodyA.LocalCenter;
|
||||
_localCenterB = BodyB.LocalCenter;
|
||||
_invMassA = BodyA.InvMass;
|
||||
_invMassB = BodyB.InvMass;
|
||||
_invIA = BodyA.InvI;
|
||||
_invIB = BodyB.InvI;
|
||||
|
||||
float aA = data.Angles[_indexA];
|
||||
var vA = data.LinearVelocities[_indexA];
|
||||
float wA = data.AngularVelocities[_indexA];
|
||||
|
||||
float aB = data.Angles[_indexB];
|
||||
var vB = data.LinearVelocities[_indexB];
|
||||
float wB = data.AngularVelocities[_indexB];
|
||||
|
||||
Quaternion2D qA = new(aA), qB = new(aB);
|
||||
|
||||
_rA = Transform.Mul(qA, LocalAnchorA - _localCenterA);
|
||||
_rB = Transform.Mul(qB, LocalAnchorB - _localCenterB);
|
||||
|
||||
// J = [-I -r1_skew I r2_skew]
|
||||
// [ 0 -1 0 1]
|
||||
// r_skew = [-ry; rx]
|
||||
|
||||
// Matlab
|
||||
// K = [ mA+r1y^2*iA+mB+r2y^2*iB, -r1y*iA*r1x-r2y*iB*r2x, -r1y*iA-r2y*iB]
|
||||
// [ -r1y*iA*r1x-r2y*iB*r2x, mA+r1x^2*iA+mB+r2x^2*iB, r1x*iA+r2x*iB]
|
||||
// [ -r1y*iA-r2y*iB, r1x*iA+r2x*iB, iA+iB]
|
||||
|
||||
float mA = _invMassA, mB = _invMassB;
|
||||
float iA = _invIA, iB = _invIB;
|
||||
|
||||
Matrix33 K;
|
||||
K.EX.X = mA + mB + _rA.Y * _rA.Y * iA + _rB.Y * _rB.Y * iB;
|
||||
K.EY.X = -_rA.Y * _rA.X * iA - _rB.Y * _rB.X * iB;
|
||||
K.EZ.X = -_rA.Y * iA - _rB.Y * iB;
|
||||
K.EX.Y = K.EY.X;
|
||||
K.EY.Y = mA + mB + _rA.X * _rA.X * iA + _rB.X * _rB.X * iB;
|
||||
K.EZ.Y = _rA.X * iA + _rB.X * iB;
|
||||
K.EX.Z = K.EZ.X;
|
||||
K.EY.Z = K.EZ.Y;
|
||||
K.EZ.Z = iA + iB;
|
||||
|
||||
if (Stiffness > 0.0f)
|
||||
{
|
||||
K.GetInverse22(ref _mass);
|
||||
|
||||
float invM = iA + iB;
|
||||
|
||||
float C = aB - aA - ReferenceAngle;
|
||||
|
||||
// Damping coefficient
|
||||
float d = Damping;
|
||||
|
||||
// Spring stiffness
|
||||
float k = Stiffness;
|
||||
|
||||
// magic formulas
|
||||
float h = data.FrameTime;
|
||||
_gamma = h * (d + h * k);
|
||||
_gamma = _gamma != 0.0f ? 1.0f / _gamma : 0.0f;
|
||||
Bias = C * h * k * _gamma;
|
||||
|
||||
invM += _gamma;
|
||||
_mass.EZ.Z = invM != 0.0f ? 1.0f / invM : 0.0f;
|
||||
}
|
||||
else if (K.EZ.Z == 0.0f)
|
||||
{
|
||||
K.GetInverse22(ref _mass);
|
||||
_gamma = 0.0f;
|
||||
Bias = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
K.GetSymInverse33(ref _mass);
|
||||
_gamma = 0.0f;
|
||||
Bias = 0.0f;
|
||||
}
|
||||
|
||||
if (data.WarmStarting)
|
||||
{
|
||||
// Scale impulses to support a variable time step.
|
||||
_impulse *= data.DtRatio;
|
||||
|
||||
var P = new Vector2(_impulse.X, _impulse.Y);
|
||||
|
||||
vA -= P * mA;
|
||||
wA -= iA * (Vector2.Cross(_rA, P) + _impulse.Z);
|
||||
|
||||
vB += P * mB;
|
||||
wB += iB * (Vector2.Cross(_rB, P) + _impulse.Z);
|
||||
}
|
||||
else
|
||||
{
|
||||
_impulse = Vector3.Zero;
|
||||
}
|
||||
|
||||
data.LinearVelocities[_indexA] = vA;
|
||||
data.AngularVelocities[_indexA] = wA;
|
||||
data.LinearVelocities[_indexB] = vB;
|
||||
data.AngularVelocities[_indexB] = wB;
|
||||
}
|
||||
|
||||
internal override void SolveVelocityConstraints(SolverData data)
|
||||
{
|
||||
var vA = data.LinearVelocities[_indexA];
|
||||
float wA = data.AngularVelocities[_indexA];
|
||||
var vB = data.LinearVelocities[_indexB];
|
||||
float wB = data.AngularVelocities[_indexB];
|
||||
|
||||
float mA = _invMassA, mB = _invMassB;
|
||||
float iA = _invIA, iB = _invIB;
|
||||
|
||||
if (Stiffness > 0.0f)
|
||||
{
|
||||
float Cdot2 = wB - wA;
|
||||
|
||||
float impulse2 = -_mass.EZ.Z * (Cdot2 + Bias + _gamma * _impulse.Z);
|
||||
_impulse.Z += impulse2;
|
||||
|
||||
wA -= iA * impulse2;
|
||||
wB += iB * impulse2;
|
||||
|
||||
var Cdot1 = vB + Vector2.Cross(wB, _rB) - vA - Vector2.Cross(wA, _rA);
|
||||
|
||||
var impulse1 = -_mass.Mul22(Cdot1);
|
||||
_impulse.X += impulse1.X;
|
||||
_impulse.Y += impulse1.Y;
|
||||
|
||||
var P = impulse1;
|
||||
|
||||
vA -= P * mA;
|
||||
wA -= iA * Vector2.Cross(_rA, P);
|
||||
|
||||
vB += P * mB;
|
||||
wB += iB * Vector2.Cross(_rB, P);
|
||||
}
|
||||
else
|
||||
{
|
||||
var Cdot1 = vB + Vector2.Cross(wB, _rB) - vA - Vector2.Cross(wA, _rA);
|
||||
float Cdot2 = wB - wA;
|
||||
var Cdot = new Vector3(Cdot1.X, Cdot1.Y, Cdot2);
|
||||
|
||||
var impulse = -_mass.Mul(Cdot);
|
||||
_impulse += impulse;
|
||||
|
||||
var P = new Vector2(impulse.X, impulse.Y);
|
||||
|
||||
vA -= P * mA;
|
||||
wA -= iA * (Vector2.Cross(_rA, P) + impulse.Z);
|
||||
|
||||
vB += P * mB;
|
||||
wB += iB * (Vector2.Cross(_rB, P) + impulse.Z);
|
||||
}
|
||||
|
||||
data.LinearVelocities[_indexA] = vA;
|
||||
data.AngularVelocities[_indexA] = wA;
|
||||
data.LinearVelocities[_indexB] = vB;
|
||||
data.AngularVelocities[_indexB] = wB;
|
||||
}
|
||||
|
||||
internal override bool SolvePositionConstraints(SolverData data)
|
||||
{
|
||||
var cA = data.Positions[_indexA];
|
||||
float aA = data.Angles[_indexA];
|
||||
var cB = data.Positions[_indexB];
|
||||
float aB = data.Angles[_indexB];
|
||||
|
||||
Quaternion2D qA = new(aA), qB = new(aB);
|
||||
|
||||
float mA = _invMassA, mB = _invMassB;
|
||||
float iA = _invIA, iB = _invIB;
|
||||
|
||||
var rA = Transform.Mul(qA, LocalAnchorA - _localCenterA);
|
||||
var rB = Transform.Mul(qB, LocalAnchorB - _localCenterB);
|
||||
|
||||
float positionError, angularError;
|
||||
|
||||
Matrix33 K;
|
||||
K.EX.X = mA + mB + rA.Y * rA.Y * iA + rB.Y * rB.Y * iB;
|
||||
K.EY.X = -rA.Y * rA.X * iA - rB.Y * rB.X * iB;
|
||||
K.EZ.X = -rA.Y * iA - rB.Y * iB;
|
||||
K.EX.Y = K.EY.X;
|
||||
K.EY.Y = mA + mB + rA.X * rA.X * iA + rB.X * rB.X * iB;
|
||||
K.EZ.Y = rA.X * iA + rB.X * iB;
|
||||
K.EX.Z = K.EZ.X;
|
||||
K.EY.Z = K.EZ.Y;
|
||||
K.EZ.Z = iA + iB;
|
||||
|
||||
if (Stiffness > 0.0f)
|
||||
{
|
||||
var C1 = cB + rB - cA - rA;
|
||||
|
||||
positionError = C1.Length;
|
||||
angularError = 0.0f;
|
||||
|
||||
var P = -K.Solve22(C1);
|
||||
|
||||
cA -= P * mA;
|
||||
aA -= iA * Vector2.Cross(rA, P);
|
||||
|
||||
cB += P * mB;
|
||||
aB += iB * Vector2.Cross(rB, P);
|
||||
}
|
||||
else
|
||||
{
|
||||
var C1 = cB + rB - cA - rA;
|
||||
float C2 = aB - aA - ReferenceAngle;
|
||||
|
||||
positionError = C1.Length;
|
||||
angularError = Math.Abs(C2);
|
||||
|
||||
Vector3 C = new(C1.X, C1.Y, C2);
|
||||
|
||||
Vector3 impulse;
|
||||
if (K.EZ.Z > 0.0f)
|
||||
{
|
||||
impulse = -K.Solve33(C);
|
||||
}
|
||||
else
|
||||
{
|
||||
var impulse2 = -K.Solve22(C1);
|
||||
impulse = new Vector3(impulse2.X, impulse2.Y, 0.0f);
|
||||
}
|
||||
|
||||
var P = new Vector2(impulse.X, impulse.Y);
|
||||
|
||||
cA -= P * mA;
|
||||
aA -= iA * (Vector2.Cross(rA, P) + impulse.Z);
|
||||
|
||||
cB += P * mB;
|
||||
aB += iB * (Vector2.Cross(rB, P) + impulse.Z);
|
||||
}
|
||||
|
||||
data.Positions[_indexA] = cA;
|
||||
data.Angles[_indexA]= aA;
|
||||
data.Positions[_indexB] = cB;
|
||||
data.Angles[_indexB] = aB;
|
||||
|
||||
return positionError <= data.LinearSlop && angularError <= data.AngularSlop;
|
||||
}
|
||||
|
||||
public bool Equals(WeldJoint? other)
|
||||
{
|
||||
if (ReferenceEquals(null, other)) return false;
|
||||
if (ReferenceEquals(this, other)) return true;
|
||||
if (!base.Equals(other)) return false;
|
||||
|
||||
return Stiffness.Equals(other.Stiffness) &&
|
||||
Damping.Equals(other.Damping) &&
|
||||
Bias.Equals(other.Bias);
|
||||
}
|
||||
}
|
||||
}
|
||||
38
Robust.Shared/Physics/Dynamics/MassData.cs
Normal file
38
Robust.Shared/Physics/Dynamics/MassData.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
// 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.
|
||||
|
||||
using Robust.Shared.Maths;
|
||||
|
||||
namespace Robust.Shared.Physics.Dynamics
|
||||
{
|
||||
public struct MassData
|
||||
{
|
||||
/// The mass of the shape, usually in kilograms.
|
||||
public float Mass;
|
||||
|
||||
/// The position of the shape's centroid relative to the shape's origin.
|
||||
public Vector2 Center;
|
||||
|
||||
/// The rotational inertia of the shape about the local origin.
|
||||
public float I;
|
||||
}
|
||||
}
|
||||
@@ -159,8 +159,6 @@ stored in a single array since multiple arrays lead to multiple misses.
|
||||
private float _maxAngularVelocity;
|
||||
private float _maxLinearCorrection;
|
||||
private float _maxAngularCorrection;
|
||||
private float _linearSlop;
|
||||
private float _angularSlop;
|
||||
private int _positionIterations;
|
||||
private bool _sleepAllowed; // BONAFIDE MONAFIED
|
||||
private float _timeToSleep;
|
||||
@@ -234,8 +232,6 @@ stored in a single array since multiple arrays lead to multiple misses.
|
||||
_positionIterations = cfg.PositionIterations;
|
||||
_sleepAllowed = cfg.SleepAllowed;
|
||||
_timeToSleep = cfg.TimeToSleep;
|
||||
_linearSlop = cfg.LinearSlop;
|
||||
_angularSlop = cfg.AngularSlop;
|
||||
|
||||
_contactSolver.LoadConfig(cfg);
|
||||
}
|
||||
@@ -337,7 +333,7 @@ stored in a single array since multiple arrays lead to multiple misses.
|
||||
|
||||
// Didn't use the old variable names because they're hard to read
|
||||
var transform = _physicsManager.EnsureTransform(body);
|
||||
var position = transform.Position;
|
||||
var position = Transform.Mul(transform, body.LocalCenter);
|
||||
// DebugTools.Assert(!float.IsNaN(position.X) && !float.IsNaN(position.Y));
|
||||
var angle = transform.Quaternion2D.Angle;
|
||||
|
||||
@@ -373,8 +369,6 @@ stored in a single array since multiple arrays lead to multiple misses.
|
||||
SolverData.InvDt = invDt;
|
||||
SolverData.IslandIndex = ID;
|
||||
SolverData.WarmStarting = _warmStarting;
|
||||
SolverData.LinearSlop = _linearSlop;
|
||||
SolverData.AngularSlop = _angularSlop;
|
||||
SolverData.MaxLinearCorrection = _maxLinearCorrection;
|
||||
SolverData.MaxAngularCorrection = _maxAngularCorrection;
|
||||
|
||||
@@ -516,6 +510,9 @@ stored in a single array since multiple arrays lead to multiple misses.
|
||||
// Temporary NaN guards until PVS is fixed.
|
||||
if (!float.IsNaN(bodyPos.X) && !float.IsNaN(bodyPos.Y))
|
||||
{
|
||||
var q = new Quaternion2D(angle);
|
||||
|
||||
bodyPos -= Transform.Mul(q, body.LocalCenter);
|
||||
// body.Sweep.Center = bodyPos;
|
||||
// body.Sweep.Angle = angle;
|
||||
|
||||
|
||||
@@ -20,26 +20,78 @@
|
||||
* 3. This notice may not be removed or altered from any source distribution.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Physics.Dynamics
|
||||
{
|
||||
/// This describes the motion of a body/shape for TOI computation.
|
||||
/// Shapes are defined with respect to the body origin, which may
|
||||
/// no coincide with the center of mass. However, to support dynamics
|
||||
/// we must interpolate the center of mass position.
|
||||
internal struct Sweep
|
||||
{
|
||||
/// <summary>
|
||||
/// Local center of mass position
|
||||
/// </summary>
|
||||
public Vector2 LocalCenter;
|
||||
|
||||
// AKA A in box2d
|
||||
public float Angle;
|
||||
|
||||
// AKA A0 in box2d
|
||||
/// <summary>
|
||||
/// Fraction of the current time step in the range [0,1]
|
||||
/// c0 and a0 are the positions at alpha0.
|
||||
/// </summary>
|
||||
public float Angle0;
|
||||
|
||||
public float Alpha0;
|
||||
|
||||
/// <summary>
|
||||
/// Generally reflects the body's worldposition but not always.
|
||||
/// Can be used to temporarily store it during CCD.
|
||||
/// </summary>
|
||||
// AKA C in box2d
|
||||
public Vector2 Center;
|
||||
|
||||
public float Center0;
|
||||
// AKA C0 in box2d
|
||||
public Vector2 Center0;
|
||||
|
||||
// I Didn't copy LocalCenter because it's also on the Body and it will normally be rarely set sooooo
|
||||
/// <summary>
|
||||
/// Get the interpolated transform at a specific time.
|
||||
/// </summary>
|
||||
/// <param name="beta">beta is a factor in [0,1], where 0 indicates alpha0.</param>
|
||||
/// <returns>the output transform</returns>
|
||||
public Transform GetTransform(float beta)
|
||||
{
|
||||
var xf = new Transform(Center0 * (1.0f - beta) + Center * beta, (1.0f - beta) * Angle0 + beta * Angle);
|
||||
|
||||
// Shift to origin
|
||||
xf.Position -= Transform.Mul(xf.Quaternion2D, LocalCenter);
|
||||
|
||||
return xf;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Advance the sweep forward, yielding a new initial state.
|
||||
/// </summary>
|
||||
/// <param name="alpha">the new initial time.</param>
|
||||
public void Advance(float alpha)
|
||||
{
|
||||
DebugTools.Assert(Alpha0 < 1.0f);
|
||||
float beta = (alpha - Alpha0) / (1.0f - Alpha0);
|
||||
Center0 += (Center - Center0) * beta;
|
||||
Angle0 += beta * (Angle - Angle0);
|
||||
Alpha0 = alpha;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Normalize the angles.
|
||||
/// </summary>
|
||||
public void Normalize()
|
||||
{
|
||||
float twoPi = 2.0f * MathF.PI;
|
||||
float d = twoPi * MathF.Floor(Angle0 / twoPi);
|
||||
Angle0 -= d;
|
||||
Angle -= d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,8 +99,6 @@ namespace Robust.Shared.Physics
|
||||
CfgVar(CVars.TimeToSleep, value => _islandCfg.TimeToSleep = value);
|
||||
CfgVar(CVars.VelocityThreshold, value => _islandCfg.VelocityThreshold = value);
|
||||
CfgVar(CVars.Baumgarte, value => _islandCfg.Baumgarte = value);
|
||||
CfgVar(CVars.LinearSlop, value => _islandCfg.LinearSlop = value);
|
||||
CfgVar(CVars.AngularSlop, value => _islandCfg.AngularSlop = value);
|
||||
CfgVar(CVars.MaxLinearCorrection, value => _islandCfg.MaxLinearCorrection = value);
|
||||
CfgVar(CVars.MaxAngularCorrection, value => _islandCfg.MaxAngularCorrection = value);
|
||||
CfgVar(CVars.VelocityConstraintsPerThread, value => _islandCfg.VelocityConstraintsPerThread = value);
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
namespace Robust.Shared.Physics
|
||||
using System;
|
||||
|
||||
namespace Robust.Shared.Physics
|
||||
{
|
||||
internal static class PhysicsConstants
|
||||
{
|
||||
@@ -8,8 +10,18 @@
|
||||
/// Making it larger may create artifacts for vertex collision.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Default is set to be 2 x linearslop. TODO Should we listen to linearslop changes?
|
||||
/// Default is set to be 2 x linearslop.
|
||||
/// </remarks>
|
||||
public const float PolygonRadius = 2 * 0.005f;
|
||||
public const float PolygonRadius = 2 * LinearSlop;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum buffer distance for positions.
|
||||
/// </summary>
|
||||
public const float LinearSlop = 0.005f;
|
||||
|
||||
/// <summary>
|
||||
/// Minimum buffer distance for angles.
|
||||
/// </summary>
|
||||
public const float AngularSlop = 2.0f / 180.0f * MathF.PI;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using System;
|
||||
using Robust.Shared.Maths;
|
||||
using Robust.Shared.Physics.Collision.Shapes;
|
||||
using Robust.Shared.Physics.Dynamics;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared.Physics
|
||||
{
|
||||
@@ -10,6 +12,8 @@ namespace Robust.Shared.Physics
|
||||
/// Returns whether a particular point intersects the specified shape.
|
||||
/// </summary>
|
||||
bool TestPoint(IPhysShape shape, Transform xform, Vector2 worldPoint);
|
||||
|
||||
void GetMassData(IPhysShape shape, ref MassData data);
|
||||
}
|
||||
|
||||
public class ShapeManager : IShapeManager
|
||||
@@ -43,5 +47,110 @@ namespace Robust.Shared.Physics
|
||||
throw new ArgumentOutOfRangeException($"No implemented TestPoint for {shape.GetType()}");
|
||||
}
|
||||
}
|
||||
|
||||
public void GetMassData(IPhysShape shape, ref MassData data)
|
||||
{
|
||||
DebugTools.Assert(data.Mass > 0f);
|
||||
|
||||
// Box2D just calls fixture.GetMassData which just calls the shape method anyway soooo
|
||||
// we can just cut out the middle-man
|
||||
switch (shape)
|
||||
{
|
||||
case EdgeShape edge:
|
||||
data.Mass = 0.0f;
|
||||
data.Center = (edge.Vertex1 + edge.Vertex2) * 0.5f;
|
||||
data.I = 0.0f;
|
||||
break;
|
||||
case PhysShapeCircle circle:
|
||||
// massData->mass = density * b2_pi * m_radius * m_radius;
|
||||
data.Center = circle.Position;
|
||||
|
||||
// inertia about the local origin
|
||||
data.I = data.Mass * (0.5f * circle.Radius * circle.Radius + Vector2.Dot(circle.Position, circle.Position));
|
||||
break;
|
||||
case PhysShapeAabb aabb:
|
||||
var polygon = (PolygonShape) aabb;
|
||||
GetMassData(polygon, ref data);
|
||||
break;
|
||||
case PolygonShape poly:
|
||||
// Polygon mass, centroid, and inertia.
|
||||
// Let rho be the polygon density in mass per unit area.
|
||||
// Then:
|
||||
// mass = rho * int(dA)
|
||||
// centroid.x = (1/mass) * rho * int(x * dA)
|
||||
// centroid.y = (1/mass) * rho * int(y * dA)
|
||||
// I = rho * int((x*x + y*y) * dA)
|
||||
//
|
||||
// We can compute these integrals by summing all the integrals
|
||||
// for each triangle of the polygon. To evaluate the integral
|
||||
// for a single triangle, we make a change of variables to
|
||||
// the (u,v) coordinates of the triangle:
|
||||
// x = x0 + e1x * u + e2x * v
|
||||
// y = y0 + e1y * u + e2y * v
|
||||
// where 0 <= u && 0 <= v && u + v <= 1.
|
||||
//
|
||||
// We integrate u from [0,1-v] and then v from [0,1].
|
||||
// We also need to use the Jacobian of the transformation:
|
||||
// D = cross(e1, e2)
|
||||
//
|
||||
// Simplification: triangle centroid = (1/3) * (p1 + p2 + p3)
|
||||
//
|
||||
// The rest of the derivation is handled by computer algebra.
|
||||
|
||||
var count = poly.Vertices.Length;
|
||||
DebugTools.Assert(count >= 3);
|
||||
|
||||
Vector2 center = new(0.0f, 0.0f);
|
||||
float area = 0.0f;
|
||||
float I = 0.0f;
|
||||
|
||||
// Get a reference point for forming triangles.
|
||||
// Use the first vertex to reduce round-off errors.
|
||||
var s = poly.Vertices[0];
|
||||
|
||||
const float k_inv3 = 1.0f / 3.0f;
|
||||
|
||||
for (var i = 0; i < count; ++i)
|
||||
{
|
||||
// Triangle vertices.
|
||||
var e1 = poly.Vertices[i] - s;
|
||||
var e2 = i + 1 < count ? poly.Vertices[i+1] - s : poly.Vertices[0] - s;
|
||||
|
||||
float D = Vector2.Cross(e1, e2);
|
||||
|
||||
float triangleArea = 0.5f * D;
|
||||
area += triangleArea;
|
||||
|
||||
// Area weighted centroid
|
||||
center += (e1 + e2) * triangleArea * k_inv3;
|
||||
|
||||
float ex1 = e1.X, ey1 = e1.Y;
|
||||
float ex2 = e2.X, ey2 = e2.Y;
|
||||
|
||||
float intx2 = ex1*ex1 + ex2*ex1 + ex2*ex2;
|
||||
float inty2 = ey1*ey1 + ey2*ey1 + ey2*ey2;
|
||||
|
||||
I += (0.25f * k_inv3 * D) * (intx2 + inty2);
|
||||
}
|
||||
|
||||
// Total mass
|
||||
// data.Mass = density * area;
|
||||
var density = data.Mass / area;
|
||||
|
||||
// Center of mass
|
||||
DebugTools.Assert(area > float.Epsilon);
|
||||
center *= 1.0f / area;
|
||||
data.Center = center + s;
|
||||
|
||||
// Inertia tensor relative to the local origin (point s).
|
||||
data.I = density * I;
|
||||
|
||||
// Shift to center of mass then to original body origin.
|
||||
data.I += data.Mass * (Vector2.Dot(data.Center, data.Center) - Vector2.Dot(center, center));
|
||||
break;
|
||||
default:
|
||||
throw new NotImplementedException($"Cannot get MassData for {shape} as it's not implemented!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Security.Cryptography;
|
||||
using Robust.Shared.Containers;
|
||||
using Robust.Shared.GameObjects;
|
||||
using Robust.Shared.IoC;
|
||||
@@ -199,7 +200,7 @@ namespace Robust.Shared.Physics
|
||||
var uid = fixture.Body.OwnerUid;
|
||||
|
||||
// || prediction && !fixture.Body.Predict
|
||||
if (!_broadphases.Contains(uid)) continue;
|
||||
if (!_broadphaseInvMatrices.TryGetValue(uid, out var invMatrix)) continue;
|
||||
|
||||
var broadphase = fixture.Body.Broadphase!;
|
||||
DebugTools.Assert(broadphase.Owner.Transform.MapPosition.Position.Equals(Vector2.Zero));
|
||||
@@ -215,7 +216,7 @@ namespace Robust.Shared.Physics
|
||||
if (other.Fixture.Body == body || moveBuffer.ContainsKey(other)) continue;
|
||||
|
||||
// To avoid updating during iteration.
|
||||
_gridMoveBuffer[other] = other.AABB;
|
||||
_gridMoveBuffer[other] = invMatrix.TransformBox(other.AABB);
|
||||
}
|
||||
|
||||
_queryBuffer.Clear();
|
||||
@@ -526,6 +527,13 @@ namespace Robust.Shared.Physics
|
||||
CreateFixture(body, fixture);
|
||||
}
|
||||
|
||||
public void DestroyFixture(PhysicsComponent body, string id)
|
||||
{
|
||||
var fixture = body.GetFixture(id);
|
||||
if (fixture == null) return;
|
||||
DestroyFixture(body, fixture);
|
||||
}
|
||||
|
||||
public void DestroyFixture(Fixture fixture)
|
||||
{
|
||||
DestroyFixture(fixture.Body, fixture);
|
||||
|
||||
@@ -55,42 +55,6 @@ namespace Robust.Shared.Physics
|
||||
base.Initialize();
|
||||
UpdatesBefore.Add(typeof(SharedPhysicsSystem));
|
||||
SubscribeLocalEvent<JointComponent, ComponentShutdown>(HandleShutdown);
|
||||
|
||||
_configManager.OnValueChanged(CVars.WarmStarting, SetWarmStarting);
|
||||
_configManager.OnValueChanged(CVars.LinearSlop, SetLinearSlop);
|
||||
}
|
||||
|
||||
public override void Shutdown()
|
||||
{
|
||||
base.Shutdown();
|
||||
_configManager.UnsubValueChanged(CVars.WarmStarting, SetWarmStarting);
|
||||
_configManager.UnsubValueChanged(CVars.LinearSlop, SetLinearSlop);
|
||||
}
|
||||
|
||||
private void SetWarmStarting(bool value)
|
||||
{
|
||||
foreach (var joint in GetAllJoints())
|
||||
{
|
||||
switch (joint)
|
||||
{
|
||||
case DistanceJoint distance:
|
||||
distance.WarmStarting = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void SetLinearSlop(float value)
|
||||
{
|
||||
foreach (var joint in GetAllJoints())
|
||||
{
|
||||
switch (joint)
|
||||
{
|
||||
case DistanceJoint distance:
|
||||
distance.LinearSlop = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Joint> GetAllJoints()
|
||||
@@ -141,11 +105,27 @@ namespace Robust.Shared.Physics
|
||||
anchorA ??= Vector2.Zero;
|
||||
anchorB ??= Vector2.Zero;
|
||||
|
||||
var joint = new DistanceJoint(bodyA, bodyB, anchorA.Value, anchorB.Value)
|
||||
{
|
||||
WarmStarting = _configManager.GetCVar(CVars.WarmStarting),
|
||||
LinearSlop = _configManager.GetCVar(CVars.LinearSlop)
|
||||
};
|
||||
var joint = new DistanceJoint(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 worldAnchor, Vector2 worldAxis, string? id = null)
|
||||
{
|
||||
var joint = new PrismaticJoint(bodyA, bodyB, worldAnchor, worldAxis, EntityManager);
|
||||
id ??= GetJointId(joint);
|
||||
joint.ID = id;
|
||||
AddJoint(joint);
|
||||
@@ -163,18 +143,25 @@ namespace Robust.Shared.Physics
|
||||
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;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public static void LinearStiffness(
|
||||
float frequencyHertz,
|
||||
float dampingRatio,
|
||||
PhysicsComponent bodyA,
|
||||
PhysicsComponent bodyB,
|
||||
float massA,
|
||||
float massB,
|
||||
out float stiffness, out float damping)
|
||||
{
|
||||
var massA = bodyA.Mass;
|
||||
var massB = bodyB.Mass;
|
||||
|
||||
float mass;
|
||||
if (massA > 0.0f && massB > 0.0f)
|
||||
{
|
||||
|
||||
@@ -1,59 +1,71 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
using Robust.Shared.Console;
|
||||
using Robust.Shared.ContentPack;
|
||||
using Robust.Shared.Log;
|
||||
using Robust.Shared.Utility;
|
||||
|
||||
namespace Robust.Shared
|
||||
{
|
||||
internal static class ProgramShared
|
||||
{
|
||||
#if !FULL_RELEASE
|
||||
private static string FindContentRootDir(bool contentStart)
|
||||
{
|
||||
return contentStart ? "../../" : "../../../";
|
||||
}
|
||||
namespace Robust.Shared;
|
||||
|
||||
private static string FindEngineRootDir(bool contentStart)
|
||||
internal static class ProgramShared
|
||||
{
|
||||
public static void RunExecCommands(IConsoleHost consoleHost, IReadOnlyList<string>? commands)
|
||||
{
|
||||
if (commands == null)
|
||||
return;
|
||||
|
||||
foreach (var cmd in commands)
|
||||
{
|
||||
return contentStart ? "../../RobustToolbox/" : "../../";
|
||||
consoleHost.ExecuteCommand(cmd);
|
||||
}
|
||||
}
|
||||
|
||||
#if !FULL_RELEASE
|
||||
private static string FindContentRootDir(bool contentStart)
|
||||
{
|
||||
return contentStart ? "../../" : "../../../";
|
||||
}
|
||||
|
||||
private static string FindEngineRootDir(bool contentStart)
|
||||
{
|
||||
return contentStart ? "../../RobustToolbox/" : "../../";
|
||||
}
|
||||
#endif
|
||||
|
||||
internal static void PrintRuntimeInfo(ISawmill sawmill)
|
||||
{
|
||||
sawmill.Debug($"Runtime: {RuntimeInformation.FrameworkDescription} {RuntimeInformation.RuntimeIdentifier}");
|
||||
sawmill.Debug($"OS: {RuntimeInformation.OSDescription} {RuntimeInformation.OSArchitecture}");
|
||||
}
|
||||
internal static void PrintRuntimeInfo(ISawmill sawmill)
|
||||
{
|
||||
sawmill.Debug($"Runtime: {RuntimeInformation.FrameworkDescription} {RuntimeInformation.RuntimeIdentifier}");
|
||||
sawmill.Debug($"OS: {RuntimeInformation.OSDescription} {RuntimeInformation.OSArchitecture}");
|
||||
}
|
||||
|
||||
internal static void DoMounts(IResourceManagerInternal res, MountOptions? options, string contentBuildDir, ResourcePath assembliesPath, bool loadContentResources = true,
|
||||
bool loader = false, bool contentStart = false)
|
||||
{
|
||||
internal static void DoMounts(IResourceManagerInternal res, MountOptions? options, string contentBuildDir, ResourcePath assembliesPath, bool loadContentResources = true,
|
||||
bool loader = false, bool contentStart = false)
|
||||
{
|
||||
#if FULL_RELEASE
|
||||
if (!loader)
|
||||
res.MountContentDirectory(@"Resources/");
|
||||
#else
|
||||
res.MountContentDirectory($@"{FindEngineRootDir(contentStart)}Resources/");
|
||||
res.MountContentDirectory($@"{FindEngineRootDir(contentStart)}Resources/");
|
||||
|
||||
if (loadContentResources)
|
||||
{
|
||||
var contentRootDir = FindContentRootDir(contentStart);
|
||||
res.MountContentDirectory($@"{contentRootDir}bin/{contentBuildDir}/", assembliesPath);
|
||||
res.MountContentDirectory($@"{contentRootDir}Resources/");
|
||||
}
|
||||
if (loadContentResources)
|
||||
{
|
||||
var contentRootDir = FindContentRootDir(contentStart);
|
||||
res.MountContentDirectory($@"{contentRootDir}bin/{contentBuildDir}/", assembliesPath);
|
||||
res.MountContentDirectory($@"{contentRootDir}Resources/");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (options == null)
|
||||
return;
|
||||
if (options == null)
|
||||
return;
|
||||
|
||||
foreach (var diskPath in options.DirMounts)
|
||||
{
|
||||
res.MountContentDirectory(diskPath);
|
||||
}
|
||||
foreach (var diskPath in options.DirMounts)
|
||||
{
|
||||
res.MountContentDirectory(diskPath);
|
||||
}
|
||||
|
||||
foreach (var diskPath in options.ZipMounts)
|
||||
{
|
||||
res.MountContentPack(diskPath);
|
||||
}
|
||||
foreach (var diskPath in options.ZipMounts)
|
||||
{
|
||||
res.MountContentPack(diskPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,6 +46,7 @@ namespace Robust.Shared.Utility
|
||||
|
||||
public override int Read(byte[] buffer, int offset, int count)
|
||||
{
|
||||
return Read(buffer.AsSpan(offset, count));
|
||||
var read = _wrapping.Read(buffer, offset, count);
|
||||
|
||||
if (read > 0)
|
||||
|
||||
@@ -52,21 +52,9 @@ namespace Robust.Shared.Utility
|
||||
{
|
||||
var cRead = stream.Read(buffer);
|
||||
if (cRead == 0)
|
||||
{
|
||||
throw new EndOfStreamException();
|
||||
}
|
||||
|
||||
// read += cRead;
|
||||
buffer = buffer[cRead..];
|
||||
|
||||
if (buffer.Length > 0)
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,10 @@ namespace Robust.UnitTesting.Shared.GameObjects.Systems
|
||||
{
|
||||
f.LoadString(Prototypes);
|
||||
})
|
||||
.RegisterDependencies(f =>
|
||||
{
|
||||
f.Register<IShapeManager, ShapeManager>();
|
||||
})
|
||||
.InitializeInstance();
|
||||
|
||||
var mapManager = sim.Resolve<IMapManager>();
|
||||
|
||||
Reference in New Issue
Block a user