Compare commits

...

19 Commits

Author SHA1 Message Date
metalgearsloth
75fc9089c3 Add weld joints and other misc changes for docking (#2197) 2021-11-11 19:54:08 +11:00
Kara D
0972601a43 Revert "entity creation crash fixes"
This reverts commit 603c252c48.
2021-11-10 12:09:31 -07:00
Paul
603c252c48 entity creation crash fixes 2021-11-10 18:57:47 +01:00
Paul
d5b1c044b7 fixes the crash for realsies 2021-11-10 18:34:21 +01:00
metalgearsloth
4600f0531d Fix centre of mass (#2212) 2021-11-10 17:25:38 +01:00
Leon Friedrich
c88498eca9 Add function to directly perform lookups using an EntityLookupComponent (#2200) 2021-11-11 01:12:49 +11:00
Paul Ritter
f15f1eb345 adds gridremovalevent (#2201)
Co-authored-by: Paul <ritter.paul1+git@googlemail.com>
2021-11-10 23:27:11 +11:00
Pieter-Jan Briers
5be3ced05a UI scaling for WebViewControl. 2021-11-10 11:57:58 +01:00
Pieter-Jan Briers
7f03e88e97 OSWindow not adjusts SetSize when window is resized. 2021-11-10 11:57:12 +01:00
Pieter-Jan Briers
8e3fa3e52d Pass + as command line arg to client/server to execute commands after init. 2021-11-10 02:01:31 +01:00
metalgearsloth
f9ae3e1fc2 Significantly optimise server when PVS disabled (#2196) 2021-11-10 01:38:37 +01:00
mirrorcult
bf9e95fa8a Update README.md 2021-11-09 17:21:37 -07:00
ZorenZal
030a7d265b Added set English language checkbox (#2136) 2021-11-10 01:12:36 +01:00
Pieter-Jan Briers
df70e94743 ununupdated comment 2021-11-10 01:09:06 +01:00
metalgearsloth
d68cd4d7eb Add overlay for physics COM (#2210) 2021-11-10 01:05:08 +01:00
Pieter-Jan Briers
d098881bff Make BVH tree expansion exponential.
Fixes #2215
2021-11-10 00:59:53 +01:00
ike709
b8fbe32c27 Bump Robust.Client.WebView TargetFramework to .NET 6 (#2214) 2021-11-09 20:17:37 +01:00
Pieter-Jan Briers
02d2bd31e7 Update Lidgren submodule; re-enable encryption. 2021-11-09 19:34:55 +01:00
Pieter-Jan Briers
bd0dba0df0 Disable network encryption to bandaid it for .NET 6. 2021-11-09 18:07:59 +01:00
59 changed files with 2299 additions and 699 deletions

View File

@@ -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

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -198,6 +198,8 @@ namespace Robust.Client
_client.ConnectToServer(LaunchState.ConnectEndpoint);
}
ProgramShared.RunExecCommands(_console, _commandLineArgs?.ExecCommands);
return true;
}

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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)
{

View File

@@ -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);
}
}

View File

@@ -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();

View File

@@ -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

View File

@@ -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();
}
}

View File

@@ -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,

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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);

View File

@@ -15,7 +15,6 @@ namespace Robust.Server.GameStates
/// </summary>
void SendGameStateUpdate();
bool PvsEnabled { get; set; }
float PvsRange { get; set; }
void SetTransformNetId(ushort netId);
}

View 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;
}
}
}

View 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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View 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);
}
}
}

View File

@@ -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,

View File

@@ -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;
}
}

View File

@@ -220,6 +220,9 @@ namespace Robust.Shared.GameObjects
return false;
}
/// <inheritdoc />
public int EntityCount => Entities.Count;
/// <inheritdoc />
public IEnumerable<IEntity> GetEntities() => Entities.Values;

View File

@@ -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);

View File

@@ -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)
{

View File

@@ -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>

View File

@@ -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);
}
}
}

View File

@@ -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]);
}
}
}

View File

@@ -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();

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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()

View File

@@ -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]

View File

@@ -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>

View File

@@ -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);
}
}

View File

@@ -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)

View File

@@ -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;

View File

@@ -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);

View 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);
}
}
}

View File

@@ -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;

View 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);
}
}
}

View 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;
}
}

View File

@@ -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;

View File

@@ -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;
}
}
}

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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!");
}
}
}
}

View File

@@ -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);

View File

@@ -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)
{

View File

@@ -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);
}
}
}

View File

@@ -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)

View File

@@ -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
{
}
}
}

View File

@@ -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>();