Compare commits

...

8 Commits

Author SHA1 Message Date
Acruid
a0d241e551 Removes some things that should not have been in the last PR. 2021-03-09 02:06:13 -08:00
GraniteSidewalk
33a6934582 Large number of changes to Shaders and Overlays (#1387)
* AAAAAAAAAAAAA

* Organization

* Still doesnt work

* Formatting

* It works!!

* More changes to everything

* Beginning of changes to overlays

* Makes the overlay manager GUID based (also it was very messy, still messy but i fixed some of it)

* Stencils are easy

* Questionable changes to overlays

* Minor change to HLR

* Fixed duplicate overlays when calling some commands (Like showbb)

* Fixes misleading message

* Adds a variety of worldspaces for overlays to choose from

* Caching

* Address reviews

* Merging pains

* ah.

* ahhhhh

* minor overlaymanager changes

* Work

* fix

* Merge??

* Fixes null errors

* Force update

* Delete whatever the fuck this is?

Co-authored-by: Pieter-Jan Briers <pieterjan.briers+git@gmail.com>
2021-03-09 01:52:16 -08:00
metalgearsloth
f237a8bbbc Optimise static body sleeping (#1618)
Co-authored-by: Metal Gear Sloth <metalgearsloth@gmail.com>
2021-03-09 19:47:51 +11:00
Pieter-Jan Briers
4bc775c01c RSI loader improvements:
1. Stop using NJsonSchema, it didn't do anything useful.
2. Use System.Text.Json instead of Newtonsoft.Json.
3. General cleanup of the code, using arrays instead of lists, etc...
2021-03-08 11:18:19 +01:00
Pieter-Jan Briers
93b4d81505 Optimize ImageSharp blitting. 2021-03-08 11:15:33 +01:00
Pieter-Jan Briers
0afb85a09e Fix some missing re-pooling of ImageSharp images. 2021-03-08 09:45:22 +01:00
Metal Gear Sloth
7b9315cea4 Significantly lower physics speedcap 2021-03-08 15:46:50 +11:00
Metal Gear Sloth
dc3af45096 Fix anchored message 2021-03-08 15:00:08 +11:00
35 changed files with 545 additions and 415 deletions

View File

@@ -1,8 +1,9 @@
using System;
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.Input;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared;
using Robust.Shared.Configuration;
using Robust.Shared.GameObjects;
@@ -41,14 +42,14 @@ namespace Robust.Client.Debugging
_debugColliders = value;
if (value)
if (value && !_overlayManager.HasOverlay<PhysicsOverlay>())
{
_overlayManager.AddOverlay(new PhysicsOverlay(_componentManager, _eyeManager,
_prototypeManager, _inputManager, _physicsManager));
}
else
{
_overlayManager.RemoveOverlay(nameof(PhysicsOverlay));
_overlayManager.RemoveOverlay<PhysicsOverlay>();
}
}
}
@@ -66,13 +67,13 @@ namespace Robust.Client.Debugging
_debugPositions = value;
if (value)
if (value && !_overlayManager.HasOverlay<EntityPositionOverlay>())
{
_overlayManager.AddOverlay(new EntityPositionOverlay(_entityManager, _eyeManager));
}
else
{
_overlayManager.RemoveOverlay(nameof(EntityPositionOverlay));
_overlayManager.RemoveOverlay<EntityPositionOverlay>();
}
}
}
@@ -91,8 +92,8 @@ namespace Robust.Client.Debugging
private Vector2 _hoverStartScreen = Vector2.Zero;
private List<IPhysBody> _hoverBodies = new();
public PhysicsOverlay(IComponentManager compMan, IEyeManager eyeMan, IPrototypeManager protoMan, IInputManager inputManager, IPhysicsManager physicsManager)
: base(nameof(PhysicsOverlay))
{
_componentManager = compMan;
_eyeManager = eyeMan;
@@ -265,7 +266,7 @@ namespace Robust.Client.Debugging
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public EntityPositionOverlay(IEntityManager entityManager, IEyeManager eyeManager) : base(nameof(EntityPositionOverlay))
public EntityPositionOverlay(IEntityManager entityManager, IEyeManager eyeManager)
{
_entityManager = entityManager;
_eyeManager = eyeManager;

View File

@@ -1,10 +1,11 @@
using Robust.Shared.IoC;
using Robust.Shared.IoC;
using Robust.Shared.Network.Messages;
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Maths;
using Robust.Shared.Timing;
using Robust.Shared.Enums;
using Robust.Shared.Network;
namespace Robust.Client.Debugging
@@ -38,13 +39,13 @@ namespace Robust.Client.Debugging
_debugDrawRays = value;
if (value)
if (value && !_overlayManager.HasOverlay<DebugDrawRayOverlay>())
{
_overlayManager.AddOverlay(new DebugDrawRayOverlay(this));
}
else
{
_overlayManager.RemoveOverlay(nameof(DebugDrawRayOverlay));
_overlayManager.RemoveOverlay<DebugDrawRayOverlay>();
}
}
}
@@ -81,7 +82,7 @@ namespace Robust.Client.Debugging
private readonly DebugDrawingManager _owner;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public DebugDrawRayOverlay(DebugDrawingManager owner) : base(nameof(DebugDrawRayOverlay))
public DebugDrawRayOverlay(DebugDrawingManager owner)
{
_owner = owner;
}

View File

@@ -24,6 +24,7 @@
using System;
using Robust.Client.Graphics;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
@@ -55,7 +56,7 @@ namespace Robust.Client.Debugging
IoCManager.Resolve<IOverlayManager>().AddOverlay(new PhysicsDebugOverlay(this));
if (value == PhysicsDebugFlags.None)
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(nameof(PhysicsDebugOverlay));
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(typeof(PhysicsDebugOverlay));
_flags = value;
}
@@ -119,7 +120,7 @@ namespace Robust.Client.Debugging
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public PhysicsDebugOverlay(DebugPhysicsSystem system) : base(nameof(PhysicsDebugOverlay))
public PhysicsDebugOverlay(DebugPhysicsSystem system)
{
_physics = system;
}

View File

@@ -11,6 +11,7 @@ using Robust.Shared.Log;
using Robust.Shared.Prototypes;
using Robust.Shared.Timing;
using Robust.Shared.Utility;
using Robust.Shared.Enums;
namespace Robust.Client.GameObjects
{
@@ -42,7 +43,7 @@ namespace Robust.Client.GameObjects
{
base.Shutdown();
overlayManager.RemoveOverlay("EffectSystem");
overlayManager.RemoveOverlay(typeof(EffectOverlay));
}
public void CreateEffect(EffectSystemMessage message)
@@ -329,7 +330,6 @@ namespace Robust.Client.GameObjects
{
private readonly IPlayerManager _playerManager;
public override bool AlwaysDirty => true;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly ShaderInstance _unshadedShader;
@@ -337,8 +337,7 @@ namespace Robust.Client.GameObjects
private readonly IMapManager _mapManager;
private readonly IEntityManager _entityManager;
public EffectOverlay(EffectSystem owner, IPrototypeManager protoMan, IMapManager mapMan, IPlayerManager playerMan, IEntityManager entityManager) : base(
"EffectSystem")
public EffectOverlay(EffectSystem owner, IPrototypeManager protoMan, IMapManager mapMan, IPlayerManager playerMan, IEntityManager entityManager)
{
_owner = owner;
_unshadedShader = protoMan.Index<ShaderPrototype>("unshaded").Instance();

View File

@@ -3,6 +3,7 @@ using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Configuration;
using Robust.Shared.Console;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
@@ -33,7 +34,7 @@ namespace Robust.Client.GameStates
private readonly int _lineHeight;
private readonly List<NetEntity> _netEnts = new();
public NetEntityOverlay() : base(nameof(NetEntityOverlay))
public NetEntityOverlay()
{
IoCManager.InjectDependencies(this);
var cache = IoCManager.Resolve<IResourceCache>();
@@ -179,11 +180,10 @@ namespace Robust.Client.GameStates
return Color.Green; // Entity in PVS, but not updated recently.
}
protected override void Dispose(bool disposing)
protected override void DisposeBehavior()
{
_gameStateManager.GameStateApplied -= HandleGameStateApplied;
base.Dispose(disposing);
base.DisposeBehavior();
}
private static void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str, Color textColor)
@@ -238,14 +238,14 @@ namespace Robust.Client.GameStates
var bValue = iValue > 0;
var overlayMan = IoCManager.Resolve<IOverlayManager>();
if(bValue && !overlayMan.HasOverlay(nameof(NetEntityOverlay)))
if(bValue && !overlayMan.HasOverlay(typeof(NetEntityOverlay)))
{
overlayMan.AddOverlay(new NetEntityOverlay());
shell.WriteLine("Enabled network entity report overlay.");
}
else if(!bValue && overlayMan.HasOverlay(nameof(NetEntityOverlay)))
else if(!bValue && overlayMan.HasOverlay(typeof(NetEntityOverlay)))
{
overlayMan.RemoveOverlay(nameof(NetEntityOverlay));
overlayMan.RemoveOverlay(typeof(NetEntityOverlay));
shell.WriteLine("Disabled network entity report overlay.");
}
}

View File

@@ -1,6 +1,8 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
using Robust.Shared.Enums;
using Robust.Shared.Console;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
@@ -34,7 +36,7 @@ namespace Robust.Client.GameStates
private readonly List<(GameTick Tick, int Payload, int lag, int interp)> _history = new(HistorySize+10);
public NetGraphOverlay() : base(nameof(NetGraphOverlay))
public NetGraphOverlay()
{
IoCManager.InjectDependencies(this);
var cache = IoCManager.Resolve<IResourceCache>();
@@ -142,11 +144,11 @@ namespace Robust.Client.GameStates
DrawString((DrawingHandleScreen)handle, _font, new Vector2(leftMargin, height + LowerGraphOffset), $"{_gameStateManager.CurrentBufferSize.ToString()} states");
}
protected override void Dispose(bool disposing)
protected override void DisposeBehavior()
{
_gameStateManager.GameStateApplied -= HandleGameStateApplied;
base.Dispose(disposing);
base.DisposeBehavior();
}
private void DrawString(DrawingHandleScreen handle, Font font, Vector2 pos, string str)
@@ -183,14 +185,14 @@ namespace Robust.Client.GameStates
var bValue = iValue > 0;
var overlayMan = IoCManager.Resolve<IOverlayManager>();
if(bValue && !overlayMan.HasOverlay(nameof(NetGraphOverlay)))
if(bValue && !overlayMan.HasOverlay(typeof(NetGraphOverlay)))
{
overlayMan.AddOverlay(new NetGraphOverlay());
shell.WriteLine("Enabled network overlay.");
}
else if(overlayMan.HasOverlay(nameof(NetGraphOverlay)))
else if(overlayMan.HasOverlay(typeof(NetGraphOverlay)))
{
overlayMan.RemoveOverlay(nameof(NetGraphOverlay));
overlayMan.RemoveOverlay(typeof(NetGraphOverlay));
shell.WriteLine("Disabled network overlay.");
}
}

View File

@@ -1,3 +1,4 @@
using Robust.Shared.Enums;
using Robust.Client.Graphics;
using Robust.Shared.Console;
using Robust.Shared.GameObjects;
@@ -5,6 +6,7 @@ using Robust.Shared.IoC;
using Robust.Shared.Maths;
using Robust.Shared.Physics;
using Robust.Shared.Prototypes;
using System;
using Robust.Shared.Timing;
namespace Robust.Client.GameStates
@@ -18,7 +20,7 @@ namespace Robust.Client.GameStates
public override OverlaySpace Space => OverlaySpace.WorldSpace;
private readonly ShaderInstance _shader;
public NetInterpOverlay() : base(nameof(NetInterpOverlay))
public NetInterpOverlay()
{
IoCManager.InjectDependencies(this);
_shader = _prototypeManager.Index<ShaderPrototype>("unshaded").Instance();
@@ -85,14 +87,14 @@ namespace Robust.Client.GameStates
var bValue = iValue > 0;
var overlayMan = IoCManager.Resolve<IOverlayManager>();
if (bValue && !overlayMan.HasOverlay(nameof(NetInterpOverlay)))
if (bValue && !overlayMan.HasOverlay<NetInterpOverlay>())
{
overlayMan.AddOverlay(new NetInterpOverlay());
shell.WriteLine("Enabled network interp overlay.");
}
else if (overlayMan.HasOverlay(nameof(NetInterpOverlay)))
else if (overlayMan.HasOverlay<NetInterpOverlay>())
{
overlayMan.RemoveOverlay(nameof(NetInterpOverlay));
overlayMan.RemoveOverlay<NetInterpOverlay>();
shell.WriteLine("Disabled network interp overlay.");
}
}

View File

@@ -1,4 +1,4 @@
using JetBrains.Annotations;
using JetBrains.Annotations;
using Robust.Shared.Maths;
using Robust.Shared.Utility;

View File

@@ -7,6 +7,8 @@ using Robust.Client.ResourceManagement;
using Robust.Shared.Map;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.Enums;
namespace Robust.Client.Graphics.Clyde
{
@@ -68,8 +70,7 @@ namespace Robust.Client.Graphics.Clyde
RenderOverlays(OverlaySpace.ScreenSpaceBelowWorld);
_mainViewport.Eye = _eyeManager.CurrentEye;
RenderViewport(_mainViewport);
RenderViewport(_mainViewport); //Worldspace overlays are rendered here.
{
var handle = _renderHandle.DrawingHandleScreen;
var tex = _mainViewport.RenderTarget.Texture;
@@ -107,25 +108,67 @@ namespace Robust.Client.Graphics.Clyde
list.Add(overlay);
}
}
list.Sort(OverlayComparer.Instance);
foreach (var overlay in list)
{
overlay.ClydeRender(_renderHandle, space);
}
FlushRenderQueue();
list.Sort(OverlayComparer.Instance);
foreach (var overlay in list) {
if (overlay.RequestScreenTexture) {
FlushRenderQueue();
UpdateOverlayScreenTexture(space, _mainViewport.RenderTarget);
}
if (overlay.OverwriteTargetFrameBuffer()) {
ClearFramebuffer(default);
}
overlay.ClydeRender(_renderHandle, space);
FlushRenderQueue();
}
}
}
private void DrawEntitiesAndWorldOverlay(Viewport viewport, Box2 worldBounds)
private ClydeTexture? ScreenBufferTexture;
private GLHandle screenBufferHandle;
private Vector2 lastFrameSize;
/// <summary>
/// Sends SCREEN_TEXTURE to all overlays in the given OverlaySpace that request it.
/// </summary>
private bool UpdateOverlayScreenTexture(OverlaySpace space, RenderTexture texture) {
//This currently does NOT consider viewports and just grabs the current screen framebuffer. This will need to be improved upon in the future.
List<Overlay> oTargets = new List<Overlay>();
foreach (var overlay in _overlayManager.AllOverlays) {
if (overlay.RequestScreenTexture && overlay.Space == space) {
oTargets.Add(overlay);
}
}
if (oTargets.Count > 0 && ScreenBufferTexture != null) {
if (lastFrameSize != _framebufferSize) {
GL.BindTexture(TextureTarget.Texture2D, screenBufferHandle.Handle);
GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Srgb8Alpha8, _framebufferSize.X, _framebufferSize.Y, 0,
PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero);
}
lastFrameSize = _framebufferSize;
CopyRenderTextureToTexture(texture, ScreenBufferTexture);
foreach (Overlay overlay in oTargets) {
overlay.ScreenTexture = ScreenBufferTexture;
}
oTargets.Clear();
return true;
}
return false;
}
private void DrawEntities(Viewport viewport, Box2 worldBounds)
{
if (_eyeManager.CurrentMap == MapId.Nullspace || !_mapManager.HasMapEntity(_eyeManager.CurrentMap))
{
return;
}
RenderOverlays(OverlaySpace.WorldSpaceBelowEntities);
var screenSize = viewport.Size;
// So we could calculate the correct size of the entities based on the contents of their sprite...
@@ -267,14 +310,6 @@ namespace Robust.Client.Graphics.Clyde
_drawingSpriteList.Clear();
FlushRenderQueue();
// Cleanup remainders
foreach (var overlay in worldOverlays)
{
overlay.ClydeRender(_renderHandle, OverlaySpace.WorldSpace);
}
FlushRenderQueue();
}
[MethodImpl(MethodImplOptions.NoInlining)]
@@ -368,12 +403,21 @@ namespace Robust.Client.Graphics.Clyde
// We will also render worldspace overlays here so we can do them under / above entities as necessary
using (DebugGroup("Entities"))
{
DrawEntitiesAndWorldOverlay(viewport, worldBounds);
DrawEntities(viewport, worldBounds);
}
RenderOverlays(OverlaySpace.WorldSpaceBelowFOV);
if (_lightManager.Enabled && _lightManager.DrawHardFov && eye.DrawFov)
{
GL.Clear(ClearBufferMask.StencilBufferBit);
GL.Enable(EnableCap.StencilTest);
GL.StencilOp(OpenToolkit.Graphics.OpenGL4.StencilOp.Keep, OpenToolkit.Graphics.OpenGL4.StencilOp.Keep, OpenToolkit.Graphics.OpenGL4.StencilOp.Replace);
GL.StencilFunc(StencilFunction.Always, 1, 0xFF);
GL.StencilMask(0xFF);
ApplyFovToBuffer(viewport, eye);
GL.StencilMask(0x00);
GL.Disable(EnableCap.StencilTest);
}
}
@@ -401,6 +445,14 @@ namespace Robust.Client.Graphics.Clyde
viewport.WallBleedIntermediateRenderTarget2.Texture,
UIBox2.FromDimensions(Vector2.Zero, ScreenSize), new Color(1, 1, 1, 0.5f));
}
RenderOverlays(OverlaySpace.WorldSpace);
GL.StencilFunc(StencilFunction.Notequal, 1, 0xFF);
GL.Disable(EnableCap.DepthTest);
RenderOverlays(OverlaySpace.WorldSpaceFOVStencil);
GL.Disable(EnableCap.StencilTest);
}
PopRenderStateFull(state);

View File

@@ -5,6 +5,7 @@ using OpenToolkit.Graphics.OpenGL4;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using SixLabors.ImageSharp.PixelFormats;
using ES20 = OpenToolkit.Graphics.ES20;
namespace Robust.Client.Graphics.Clyde
@@ -31,6 +32,27 @@ namespace Robust.Client.Graphics.Clyde
CheckGlError();
GL.BindTexture(TextureTarget.Texture2D, glHandle.Handle);
CheckGlError();
GL.ActiveTexture(TextureUnit.Texture0);
}
private void CopyRenderTextureToTexture(RenderTexture source, ClydeTexture target) {
LoadedRenderTarget sourceLoaded = RtToLoaded(source);
bool pause = sourceLoaded != _currentBoundRenderTarget;
FullStoredRendererState? store = null;
if (pause) {
store = PushRenderStateFull();
BindRenderTargetFull(source);
CheckGlError();
}
GL.BindTexture(TextureTarget.Texture2D, _loadedTextures[target.TextureId].OpenGLObject.Handle);
CheckGlError();
GL.CopyTexSubImage2D(TextureTarget.Texture2D, 0, 0, 0, 0, 0, _framebufferSize.X, _framebufferSize.Y);
CheckGlError();
if (pause && store != null) {
PopRenderStateFull((FullStoredRendererState)store);
}
}
private static long EstPixelSize(PixelInternalFormat format)

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Runtime.CompilerServices;

View File

@@ -280,7 +280,7 @@ namespace Robust.Client.Graphics.Clyde
}
/// <summary>
/// Flush the render handle, processing and re-pooling all the command lists.
/// Flushes the render handle, processing and re-pooling all the command lists.
/// </summary>
private void FlushRenderQueue()
{
@@ -371,6 +371,7 @@ namespace Robust.Client.Graphics.Clyde
program.Use();
int textureUnitVal = 0;
// Assign shader parameters to uniform since they may be dirty.
foreach (var (name, value) in instance.Parameters)
{
@@ -413,6 +414,15 @@ namespace Robust.Client.Graphics.Clyde
case Matrix4 matrix4:
program.SetUniform(name, matrix4);
break;
case ClydeTexture clydeTexture:
//It's important to start at Texture6 here since DrawCommandBatch uses Texture0 and Texture1 immediately after calling this
//function! If passing in textures as uniforms ever stops working it might be since someone made it use all the way up to Texture6 too.
//Might change this in the future?
TextureUnit cTarget = TextureUnit.Texture6+textureUnitVal;
SetTexture(cTarget, ((ClydeTexture)clydeTexture).TextureId);
program.SetUniformTexture(name, cTarget);
textureUnitVal++;
break;
default:
throw new InvalidOperationException($"Unable to handle shader parameter {name}: {value}");
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
@@ -428,7 +428,8 @@ namespace Robust.Client.Graphics.Clyde
private protected override void SetParameterImpl(string name, Texture value)
{
throw new NotImplementedException();
var data = Parent._shaderInstances[Handle];
data.Parameters[name] = value;
}
private protected override void SetStencilOpImpl(StencilOp op)

View File

@@ -56,19 +56,19 @@ namespace Robust.Client.Graphics.Clyde
{
// Disable sRGB so stuff doesn't get interpreter wrong.
actualParams.Srgb = false;
var img = ApplyA8Swizzle((Image<A8>) (object) image);
using var img = ApplyA8Swizzle((Image<A8>) (object) image);
return LoadTextureFromImage(img, name, loadParams);
}
if (pixelType == typeof(L8) && !actualParams.Srgb)
{
var img = ApplyL8Swizzle((Image<L8>) (object) image);
using var img = ApplyL8Swizzle((Image<L8>) (object) image);
return LoadTextureFromImage(img, name, loadParams);
}
}
// Flip image because OpenGL reads images upside down.
var copy = FlipClone(image);
using var copy = FlipClone(image);
var texture = CreateBaseTextureInternal<T>(image.Width, image.Height, actualParams, name);

View File

@@ -315,6 +315,11 @@ namespace Robust.Client.Graphics.Clyde
UniformConstantsUBO = new GLUniformBuffer<UniformConstants>(this, BindingIndexUniformConstants, nameof(UniformConstantsUBO));
CreateMainViewport();
screenBufferHandle = new GLHandle(GL.GenTexture());
GL.BindTexture(TextureTarget.Texture2D, screenBufferHandle.Handle);
ApplySampleParameters(TextureSampleParameters.Default);
ScreenBufferTexture = GenTexture(screenBufferHandle, _framebufferSize, true, null, TexturePixelType.Rgba32);
}
private void CreateMainViewport()

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using OpenToolkit.Graphics.OpenGL4;

View File

@@ -1,22 +1,29 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using JetBrains.Annotations;
using Robust.Shared.Timing;
namespace Robust.Client.Graphics
{
[PublicAPI]
public interface IOverlayManager
{
void AddOverlay(Overlay overlay);
void RemoveOverlay(string id);
bool HasOverlay(string id);
bool AddOverlay(Overlay overlay);
Overlay GetOverlay(string id);
T GetOverlay<T>(string id) where T : Overlay;
bool RemoveOverlay(Overlay overlay);
bool RemoveOverlay(Type overlayClass);
bool RemoveOverlay<T>() where T : Overlay;
bool TryGetOverlay(string id, [NotNullWhen(true)] out Overlay? overlay);
bool TryGetOverlay<T>(string id, [NotNullWhen(true)] out T? overlay) where T : Overlay;
bool TryGetOverlay(Type overlayClass, out Overlay? overlay);
bool TryGetOverlay<T>(out T? overlay) where T : Overlay;
Overlay GetOverlay(Type overlayClass);
T GetOverlay<T>() where T : Overlay;
bool HasOverlay(Type overlayClass);
bool HasOverlay<T>() where T : Overlay;
IEnumerable<Overlay> AllOverlays { get; }
}

View File

@@ -1,65 +1,54 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Robust.Shared.IoC;
using JetBrains.Annotations;
using Robust.Shared.Timing;
using Robust.Shared.Enums;
using System;
namespace Robust.Client.Graphics
{
/// <summary>
/// An overlay is used for fullscreen drawing in the game, for example parallax.
/// An overlay is used for fullscreen drawing in the game. This can range from drawing parallax to a full screen shader.
/// </summary>
[PublicAPI]
public abstract class Overlay
{
/// <summary>
/// The ID of this overlay. This is used to identify it inside the <see cref="IOverlayManager"/>.
/// Determines when this overlay is drawn in the rendering queue.
/// </summary>
public string ID { get; }
public virtual bool AlwaysDirty => false;
public bool IsDirty => AlwaysDirty || _isDirty;
public bool Drawing { get; private set; }
public virtual OverlaySpace Space => OverlaySpace.ScreenSpace;
/// <summary>
/// If set to true, <see cref="ScreenTexture"/> will be set to the current frame (at the moment before the overlay is rendered). This can be costly to performance, but
/// some shaders will require it as a passed in uniform to operate.
/// </summary>
public virtual bool RequestScreenTexture => false;
/// <summary>
/// If <see cref="RequestScreenTexture"> is true, then this will be set to the texture corresponding to the current frame. If false, it will always be null.
/// </summary>
public Texture? ScreenTexture = null;
/// <summary>
/// Overlays on the same OverlaySpace will be drawn from lowest ZIndex to highest ZIndex. As an example, ZIndex -1 will be drawn before ZIndex 2.
/// This value is 0 by default. Overlays with same ZIndex will be drawn in an random order.
/// </summary>
public int? ZIndex { get; set; }
protected IOverlayManager OverlayManager { get; }
public int? ZIndex { get; set; }
private bool Disposed = false;
public virtual bool SubHandlesUseMainShader { get; } = true;
private bool _isDirty = true;
private readonly List<DrawingHandleBase> TempHandles = new();
private bool Disposed;
protected Overlay(string id)
public Overlay()
{
OverlayManager = IoCManager.Resolve<IOverlayManager>();
ID = id;
}
public void Dispose()
{
if (Disposed)
{
return;
}
Dispose(true);
Disposed = true;
GC.SuppressFinalize(this);
}
~Overlay()
{
Dispose(false);
}
protected virtual void Dispose(bool disposing)
{
/// <summary>
/// If this function returns true, the target framebuffer will be wiped before applying this overlay to it.
/// </summary>
public virtual bool OverwriteTargetFrameBuffer(){
return false;
}
/// <summary>
@@ -69,50 +58,34 @@ namespace Robust.Client.Graphics
/// <param name="currentSpace">Current space that is being drawn. This function is called for every space that was set up in initialization.</param>
protected abstract void Draw(DrawingHandleBase handle, OverlaySpace currentSpace);
public void Dirty()
{
_isDirty = true;
protected internal virtual void FrameUpdate(FrameEventArgs args) { }
~Overlay() {
Dispose();
}
public void Dispose() {
if (Disposed)
return;
else
DisposeBehavior();
}
protected virtual void DisposeBehavior(){
Disposed = true;
GC.SuppressFinalize(this);
}
protected internal virtual void FrameUpdate(FrameEventArgs args) { }
internal void ClydeRender(IRenderHandle renderHandle, OverlaySpace currentSpace)
{
DrawingHandleBase handle;
if (currentSpace == OverlaySpace.WorldSpace)
handle = renderHandle.DrawingHandleWorld;
else
if (currentSpace == OverlaySpace.ScreenSpace || currentSpace == OverlaySpace.ScreenSpaceBelowWorld)
handle = renderHandle.DrawingHandleScreen;
else
handle = renderHandle.DrawingHandleWorld;
Draw(handle, currentSpace);
}
}
/// <summary>
/// Determines in which canvas layers an overlay gets drawn.
/// </summary>
[Flags]
public enum OverlaySpace : byte
{
/// <summary>
/// Used for matching bit flags.
/// </summary>
None = 0b0000,
/// <summary>
/// This overlay will be drawn in the UI root, thus being in screen space.
/// </summary>
ScreenSpace = 0b0001,
/// <summary>
/// This overlay will be drawn in the world root, thus being in world space.
/// </summary>
WorldSpace = 0b0010,
/// <summary>
/// Drawn in screen coordinates, but behind the world.
/// </summary>
ScreenSpaceBelowWorld = 0b0100,
}
}

View File

@@ -1,13 +1,15 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Robust.Shared.Log;
using Robust.Shared.Timing;
namespace Robust.Client.Graphics
{
internal class OverlayManager : IOverlayManagerInternal
{
private readonly Dictionary<string, Overlay> _overlays = new();
private readonly Dictionary<Type, Overlay> _overlays = new Dictionary<Type, Overlay>();
public IEnumerable<Overlay> AllOverlays => _overlays.Values;
public void FrameUpdate(FrameEventArgs args)
{
@@ -17,59 +19,80 @@ namespace Robust.Client.Graphics
}
}
public void AddOverlay(Overlay overlay)
public bool AddOverlay(Overlay overlay)
{
if (_overlays.ContainsKey(overlay.ID))
{
throw new InvalidOperationException($"We already have an overlay with ID '{overlay.ID}'");
if(_overlays.ContainsKey(overlay.GetType()))
return false;
_overlays.Add(overlay.GetType(), overlay);
return true;
}
public bool RemoveOverlay(Type overlayClass)
{
if(!overlayClass.IsSubclassOf(typeof(Overlay))){
Logger.Error("RemoveOverlay was called with arg: " + overlayClass.ToString() + ", which is not a subclass of Overlay!");
return false;
}
_overlays.Add(overlay.ID, overlay);
return _overlays.Remove(overlayClass);
}
public Overlay GetOverlay(string id)
{
return _overlays[id];
public bool RemoveOverlay<T>() where T : Overlay{
return RemoveOverlay(typeof(T));
}
public T GetOverlay<T>(string id) where T : Overlay
{
return (T) GetOverlay(id);
public bool RemoveOverlay(Overlay overlay) {
return _overlays.Remove(overlay.GetType());
}
public bool HasOverlay(string id)
{
return _overlays.ContainsKey(id);
}
public void RemoveOverlay(string id)
public bool TryGetOverlay(Type overlayClass, [NotNullWhen(true)] out Overlay? overlay)
{
if (!_overlays.TryGetValue(id, out var overlay))
{
return;
overlay = null;
if (!overlayClass.IsSubclassOf(typeof(Overlay))){
Logger.Error("TryGetOverlay was called with arg: " + overlayClass.ToString() + ", which is not a subclass of Overlay!");
return false;
}
overlay.Dispose();
_overlays.Remove(id);
return _overlays.TryGetValue(overlayClass, out overlay);
}
public bool TryGetOverlay(string id, [NotNullWhen(true)] out Overlay? overlay)
{
return _overlays.TryGetValue(id, out overlay);
}
public bool TryGetOverlay<T>(string id, [NotNullWhen(true)] out T? overlay) where T : Overlay
{
if (_overlays.TryGetValue(id, out var value))
{
overlay = (T) value;
public bool TryGetOverlay<T>([NotNullWhen(true)] out T? overlay) where T : Overlay {
overlay = null;
if(_overlays.TryGetValue(typeof(T), out Overlay? toReturn)){
overlay = (T)toReturn;
return true;
}
overlay = default;
return false;
}
public IEnumerable<Overlay> AllOverlays => _overlays.Values;
public Overlay GetOverlay(Type overlayClass) {
return _overlays[overlayClass];
}
public T GetOverlay<T>() where T : Overlay {
return (T)_overlays[typeof(T)];
}
public bool HasOverlay(Type overlayClass) {
if (!overlayClass.IsSubclassOf(typeof(Overlay)))
Logger.Error("HasOverlay was called with arg: " + overlayClass.ToString() + ", which is not a subclass of Overlay!");
return _overlays.Remove(overlayClass);
}
public bool HasOverlay<T>() where T : Overlay {
return _overlays.Remove(typeof(T));
}
}
}

View File

@@ -19,10 +19,10 @@ namespace Robust.Client.Graphics
public sealed class State : IRsiStateLike
{
// List of delays for the frame to reach the next frame.
private readonly float[] Delays;
public readonly float[] Delays;
// 2D array for the texture to use for each animation frame at each direction.
private readonly Texture[][] Icons;
public readonly Texture[][] Icons;
internal State(Vector2i size, StateId stateId, DirectionType direction, float[] delays, Texture[][] icons)
{

View File

@@ -1,47 +0,0 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "RSI Image Format Validation Schema V1",
"description": "Robust Station Image",
"type": "object",
"definitions": {
"size": {
"type": "object",
"properties": {
"x": {"type": "integer", "minimum": 1},
"y": {"type": "integer", "minimum": 1}
},
"required": ["x","y"]
},
"directions": {
"type": "integer",
"enum": [1,4,8]
},
"state": {
"type": "object",
"properties": {
"name": {"type": "string"},
"flags": {"type": "object"}, //To be de-serialized as a Dictionary
"directions": {"$ref": "#/definitions/directions"},
"delays": {
"type": "array",
"minItems": 1,
"items": {
"type": "array",
"items": {"type": "number", "minimum": 0, "exclusiveMinimum": true} //number == float
}
}
},
"required": ["name"] //'delays' is marked as optional in the spec
}
},
"properties": {
"version": {"type": "integer", "minimum": 1, "maximum": 1},
"size": {"$ref": "#/definitions/size"},
"states": {
"type": "array",
"items": {"$ref": "#/definitions/state"},
"minItems": 1
}
},
"required": ["version","size","states"]
}

View File

@@ -2,6 +2,7 @@ using System;
using System.Collections.Generic;
using Robust.Client.Graphics;
using Robust.Shared.Console;
using Robust.Shared.Enums;
using Robust.Shared.GameObjects;
using Robust.Shared.IoC;
using Robust.Shared.Maths;
@@ -67,7 +68,7 @@ namespace Robust.Client.Physics
public override void Shutdown()
{
base.Shutdown();
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(nameof(PhysicsIslandOverlay));
IoCManager.Resolve<IOverlayManager>().RemoveOverlay(typeof(PhysicsIslandOverlay));
}
private void HandleIslandSolveMessage(IslandSolveMessage message)
@@ -94,7 +95,7 @@ namespace Robust.Client.Physics
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public PhysicsIslandOverlay() : base(nameof(PhysicsIslandOverlay))
public PhysicsIslandOverlay()
{
_islandSystem = EntitySystem.Get<DebugPhysicsIslandSystem>();
_eyeManager = IoCManager.Resolve<IEyeManager>();

View File

@@ -1,5 +1,7 @@
using Robust.Shared.Enums;
using Robust.Client.Graphics;
namespace Robust.Client.Placement
{
public partial class PlacementManager
@@ -7,10 +9,9 @@ namespace Robust.Client.Placement
internal class PlacementOverlay : Overlay
{
private readonly PlacementManager _manager;
public override bool AlwaysDirty => true;
public override OverlaySpace Space => OverlaySpace.WorldSpace;
public PlacementOverlay(PlacementManager manager) : base("placement")
public PlacementOverlay(PlacementManager manager)
{
_manager = manager;
ZIndex = 100;

View File

@@ -2,17 +2,12 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text.Json;
using JetBrains.Annotations;
using Robust.Client.Graphics;
using Robust.Client.Utility;
using Robust.Shared.Log;
using Robust.Shared.Maths;
using Robust.Shared.Utility;
using Newtonsoft.Json.Linq;
#if DEBUG
using NJsonSchema;
#endif
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
@@ -24,6 +19,14 @@ namespace Robust.Client.ResourceManagement
/// </summary>
public sealed class RSIResource : BaseResource
{
private static readonly float[] OneArray = {1};
private static readonly JsonSerializerOptions SerializerOptions =
new JsonSerializerOptions(JsonSerializerDefaults.Web)
{
AllowTrailingCommas = true
};
public RSI RSI { get; private set; } = default!;
/// <summary>
@@ -38,64 +41,36 @@ namespace Robust.Client.ResourceManagement
public override void Load(IResourceCache cache, ResourcePath path)
{
var manifestPath = path / "meta.json";
string manifestContents;
var metadata = LoadRsiMetadata(cache, path);
using (var manifestFile = cache.ContentFileRead(manifestPath))
using (var reader = new StreamReader(manifestFile))
{
manifestContents = reader.ReadToEnd();
}
var stateCount = metadata.States.Length;
var toAtlas = new StateReg[stateCount];
#if DEBUG
if (RSISchema != null)
{
var errors = RSISchema.Validate(manifestContents);
if (errors.Count != 0)
{
Logger.Error($"Unable to load RSI from '{path}', {errors.Count} errors:");
foreach (var error in errors)
{
Logger.Error("{0}", error.ToString());
}
throw new RSILoadException($"{errors.Count} errors while loading RSI. See console.");
}
}
#endif
// Ok schema validated just fine.
var manifestJson = JObject.Parse(manifestContents);
var toAtlas = new List<(Image<Rgba32> src, Texture[][] output, int[][] indices, Vector2i[][] offsets, int totalFrameCount)>();
var metaData = ParseMetaData(manifestJson);
var frameSize = metaData.Size;
var frameSize = metadata.Size;
var rsi = new RSI(frameSize, path);
var callbackOffsets = new Dictionary<RSI.StateId, Vector2i[][]>();
var callbackOffsets = new Dictionary<RSI.StateId, Vector2i[][]>(stateCount);
// Do every state.
foreach (var stateObject in metaData.States)
for (var index = 0; index < metadata.States.Length; index++)
{
ref var reg = ref toAtlas[index];
var stateObject = metadata.States[index];
// Load image from disk.
var texPath = path / (stateObject.StateId + ".png");
var stream = cache.ContentFileRead(texPath);
Image<Rgba32> image;
using (stream)
using (var stream = cache.ContentFileRead(texPath))
{
image = Image.Load<Rgba32>(stream);
reg.Src = Image.Load<Rgba32>(stream);
}
var sheetSize = new Vector2i(image.Width, image.Height);
if (sheetSize.X % frameSize.X != 0 || sheetSize.Y % frameSize.Y != 0)
if (reg.Src.Width % frameSize.X != 0 || reg.Src.Height % frameSize.Y != 0)
{
throw new RSILoadException("State image size is not a multiple of the icon size.");
}
// Load all frames into a list so we can operate on it more sanely.
var frameCount = stateObject.Delays.Sum(delayList => delayList.Length);
reg.TotalFrameCount = stateObject.Delays.Sum(delayList => delayList.Length);
var (foldedDelays, foldedIndices) = FoldDelays(stateObject.Delays);
@@ -108,15 +83,18 @@ namespace Robust.Client.ResourceManagement
callbackOffset[i] = new Vector2i[foldedIndices[0].Length];
}
reg.Output = textures;
reg.Indices = foldedIndices;
reg.Offsets = callbackOffset;
var state = new RSI.State(frameSize, stateObject.StateId, stateObject.DirType, foldedDelays, textures);
rsi.AddState(state);
toAtlas.Add((image, textures, foldedIndices, callbackOffset, frameCount));
callbackOffsets[stateObject.StateId] = callbackOffset;
}
// Poorly hacked in texture atlas support here.
var totalFrameCount = toAtlas.Sum(p => p.totalFrameCount);
var totalFrameCount = toAtlas.Sum(p => p.TotalFrameCount);
// Generate atlas.
var dimensionX = (int) MathF.Ceiling(MathF.Sqrt(totalFrameCount));
@@ -125,12 +103,13 @@ namespace Robust.Client.ResourceManagement
using var sheet = new Image<Rgba32>(dimensionX * frameSize.X, dimensionY * frameSize.Y);
var sheetIndex = 0;
foreach (var (src, _, _, _, frameCount) in toAtlas)
for (var index = 0; index < toAtlas.Length; index++)
{
ref var reg = ref toAtlas[index];
// Blit all the frames over.
for (var i = 0; i < frameCount; i++)
for (var i = 0; i < reg.TotalFrameCount; i++)
{
var srcWidth = (src.Width / frameSize.X);
var srcWidth = (reg.Src.Width / frameSize.X);
var srcColumn = i % srcWidth;
var srcRow = i / srcWidth;
var srcPos = (srcColumn * frameSize.X, srcRow * frameSize.Y);
@@ -141,23 +120,24 @@ namespace Robust.Client.ResourceManagement
var srcBox = UIBox2i.FromDimensions(srcPos, frameSize);
src.Blit(srcBox, sheet, sheetPos);
reg.Src.Blit(srcBox, sheet, sheetPos);
}
sheetIndex += frameCount;
sheetIndex += reg.TotalFrameCount;
}
// Load atlas.
var texture = Texture.LoadFromImage(sheet, path.ToString());
var sheetOffset = 0;
foreach (var (_, output, indices, offsets, frameCount) in toAtlas)
for (var toAtlasIndex = 0; toAtlasIndex < toAtlas.Length; toAtlasIndex++)
{
for (var i = 0; i < indices.Length; i++)
ref var reg = ref toAtlas[toAtlasIndex];
for (var i = 0; i < reg.Indices.Length; i++)
{
var dirIndices = indices[i];
var dirOutput = output[i];
var dirOffsets = offsets[i];
var dirIndices = reg.Indices[i];
var dirOutput = reg.Output[i];
var dirOffsets = reg.Offsets[i];
for (var j = 0; j < dirIndices.Length; j++)
{
@@ -172,12 +152,13 @@ namespace Robust.Client.ResourceManagement
}
}
sheetOffset += frameCount;
sheetOffset += reg.TotalFrameCount;
}
foreach (var (image, _, _, _, _) in toAtlas)
for (var i = 0; i < toAtlas.Length; i++)
{
image.Dispose();
ref var reg = ref toAtlas[i];
reg.Src.Dispose();
}
RSI = rsi;
@@ -188,6 +169,90 @@ namespace Robust.Client.ResourceManagement
}
}
private static RsiMetadata LoadRsiMetadata(IResourceCache cache, ResourcePath path)
{
var manifestPath = path / "meta.json";
string manifestContents;
using (var manifestFile = cache.ContentFileRead(manifestPath))
using (var reader = new StreamReader(manifestFile))
{
manifestContents = reader.ReadToEnd();
}
// Ok schema validated just fine.
var manifestJson = JsonSerializer.Deserialize<RsiJsonMetadata>(manifestContents, SerializerOptions);
if (manifestJson == null)
throw new RSILoadException("Manifest JSON was null!");
var size = manifestJson.Size;
var states = new StateMetadata[manifestJson.States.Length];
for (var stateI = 0; stateI < manifestJson.States.Length; stateI++)
{
var stateObject = manifestJson.States[stateI];
var stateName = stateObject.Name;
RSI.State.DirectionType directions;
int dirValue;
if (stateObject.Directions is { } dirVal)
{
dirValue = dirVal;
directions = dirVal switch
{
1 => RSI.State.DirectionType.Dir1,
4 => RSI.State.DirectionType.Dir4,
8 => RSI.State.DirectionType.Dir8,
_ => throw new RSILoadException($"Invalid direction: {dirValue} expected 1, 4 or 8")
};
}
else
{
dirValue = 1;
directions = RSI.State.DirectionType.Dir1;
}
// We can ignore selectors and flags for now,
// because they're not used yet!
// Get the lists of delays.
float[][] delays;
if (stateObject.Delays != null)
{
delays = stateObject.Delays;
if (delays.Length != dirValue)
{
throw new RSILoadException(
"DirectionsdirectionFramesList count does not match amount of delays specified.");
}
for (var i = 0; i < delays.Length; i++)
{
var delayList = delays[i];
if (delayList.Length == 0)
{
delays[i] = OneArray;
}
}
}
else
{
delays = new float[dirValue][];
// No delays specified, default to 1 frame per dir.
for (var i = 0; i < dirValue; i++)
{
delays[i] = OneArray;
}
}
states[stateI] = new StateMetadata(new RSI.StateId(stateName), directions, delays);
}
return new RsiMetadata(size, states);
}
/// <summary>
/// Folds a per-directional sets of animation delays
/// into an equivalent set of animation delays and indices that works for every direction.
@@ -336,109 +401,25 @@ namespace Robust.Client.ResourceManagement
return (floatDelays, arrayIndices);
}
internal static RsiMetadata ParseMetaData(JObject manifestJson)
private struct StateReg
{
var size = manifestJson["size"]!.ToObject<Vector2i>();
var states = new List<StateMetadata>();
foreach (var stateObject in manifestJson["states"]!.Cast<JObject>())
{
var stateName = stateObject["name"]!.ToObject<string>()!;
RSI.State.DirectionType directions;
int dirValue;
if (stateObject.TryGetValue("directions", out var dirJToken))
{
dirValue= dirJToken.ToObject<int>();
directions = dirValue switch
{
1 => RSI.State.DirectionType.Dir1,
4 => RSI.State.DirectionType.Dir4,
8 => RSI.State.DirectionType.Dir8,
_ => throw new RSILoadException($"Invalid direction: {dirValue} expected 1, 4 or 8")
};
}
else
{
dirValue = 1;
directions = RSI.State.DirectionType.Dir1;
}
// We can ignore selectors and flags for now,
// because they're not used yet!
// Get the lists of delays.
float[][] delays;
if (stateObject.TryGetValue("delays", out var delayToken))
{
delays = delayToken.ToObject<float[][]>()!;
if (delays.Length != dirValue)
{
throw new RSILoadException(
"DirectionsdirectionFramesList count does not match amount of delays specified.");
}
for (var i = 0; i < delays.Length; i++)
{
var delayList = delays[i];
if (delayList.Length == 0)
{
delays[i] = new float[] {1};
}
}
}
else
{
delays = new float[dirValue][];
// No delays specified, default to 1 frame per dir.
for (var i = 0; i < dirValue; i++)
{
delays[i] = new float[] {1};
}
}
states.Add(new StateMetadata(new RSI.StateId(stateName), directions, delays));
}
return new RsiMetadata(size, states);
public Image<Rgba32> Src;
public Texture[][] Output;
public int[][] Indices;
public Vector2i[][] Offsets;
public int TotalFrameCount;
}
#if DEBUG
private static readonly JsonSchema? RSISchema = GetSchema();
private static JsonSchema? GetSchema()
{
try
{
string schema;
using (var schemaStream = Assembly.GetExecutingAssembly()
.GetManifestResourceStream("Robust.Client.Graphics.RSI.RSISchema.json")!)
using (var schemaReader = new StreamReader(schemaStream))
{
schema = schemaReader.ReadToEnd();
}
return JsonSchema.FromJsonAsync(schema).Result;
}
catch (Exception e)
{
System.Console.WriteLine("Failed to load RSI JSON Schema!\n{0}", e);
return null;
}
}
#endif
internal sealed class RsiMetadata
{
public RsiMetadata(Vector2i size, List<StateMetadata> states)
public RsiMetadata(Vector2i size, StateMetadata[] states)
{
Size = size;
States = states;
}
public Vector2i Size { get; }
public List<StateMetadata> States { get; }
public StateMetadata[] States { get; }
}
internal sealed class StateMetadata
@@ -466,6 +447,17 @@ namespace Robust.Client.ResourceManagement
public float[][] Delays { get; }
}
// To be directly deserialized.
[UsedImplicitly]
private sealed record RsiJsonMetadata(Vector2i Size, StateJsonMetadata[] States)
{
}
[UsedImplicitly]
private sealed record StateJsonMetadata(string Name, int? Directions, float[][]? Delays)
{
}
}
[Serializable]

View File

@@ -14,7 +14,6 @@
<PackageReference Include="DiscordRichPresence" Version="1.0.175" />
<PackageReference Include="JetBrains.Annotations" Version="2020.3.0" PrivateAssets="All" />
<PackageReference Include="nfluidsynth" Version="0.3.1" />
<PackageReference Include="NJsonSchema" Version="10.3.8" />
<PackageReference Include="NVorbis" Version="0.10.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="SixLabors.ImageSharp" Version="1.0.3" />
@@ -39,9 +38,6 @@
<ProjectReference Include="..\Robust.Shared\Robust.Shared.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Graphics\RSI\RSISchema.json" Condition="'$(Configuration)' == 'Debug'">
<LogicalName>Robust.Client.Graphics.RSI.RSISchema.json</LogicalName>
</EmbeddedResource>
<EmbeddedResource Include="Graphics\Clyde\Shaders\*" />
</ItemGroup>

View File

@@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
@@ -67,6 +67,7 @@ namespace Robust.Client.UserInterface.CustomControls
Title = Loc.GetString("Entity Spawn Panel");
SetSize = (250, 300);
MinSize = (250, 200);

View File

@@ -42,19 +42,20 @@ namespace Robust.Client.Utility
{
var dstSpan = destination.GetPixelSpan();
var dstWidth = destination.Width;
var srcHeight = sourceRect.Height;
var srcWidth = sourceRect.Width;
var (ox, oy) = destinationOffset;
for (var y = 0; y < sourceRect.Height; y++)
for (var y = 0; y < srcHeight; y++)
{
var sourceRowOffset = sourceWidth * (y + sourceRect.Top) + sourceRect.Left;
var destRowOffset = dstWidth * (y + oy) + ox;
for (var x = 0; x < sourceRect.Width; x++)
{
var pixel = source[x + sourceRowOffset];
dstSpan[x + destRowOffset] = pixel;
}
var srcRow = source[sourceRowOffset..(sourceRowOffset + srcWidth)];
var dstRow = dstSpan[destRowOffset..(destRowOffset + srcWidth)];
srcRow.CopyTo(dstRow);
}
}

View File

@@ -1,4 +1,4 @@
using System;
using System;
using Robust.Client.GameObjects;
using Robust.Client.Graphics;
using Robust.Client.ResourceManagement;
@@ -41,6 +41,40 @@ namespace Robust.Client.Utility
return specifier.RsiStateLike().Default;
}
public static float[] FrameDelays(this SpriteSpecifier specifier) {
var resc = IoCManager.Resolve<IResourceCache>();
switch (specifier) {
case SpriteSpecifier.Rsi rsi:
if (resc.TryGetResource<RSIResource>(SpriteComponent.TextureRoot / rsi.RsiPath, out var theRsi)) {
if (theRsi.RSI.TryGetState(rsi.RsiState, out var state)) {
return state.Delays;
}
}
Logger.Error("Failed to load RSI {0}", rsi.RsiPath);
return new float[0];
default:
throw new NotImplementedException();
}
}
public static Texture[] FrameArr(this SpriteSpecifier specifier) {
var resc = IoCManager.Resolve<IResourceCache>();
switch (specifier) {
case SpriteSpecifier.Rsi rsi:
if (resc.TryGetResource<RSIResource>(SpriteComponent.TextureRoot / rsi.RsiPath, out var theRsi)) {
if (theRsi.RSI.TryGetState(rsi.RsiState, out var state)) {
return state.Icons[0];
}
}
Logger.Error("Failed to load RSI {0}", rsi.RsiPath);
return new Texture[0];
default:
throw new NotImplementedException();
}
}
public static IDirectionalTextureProvider DirFrame0(this SpriteSpecifier specifier)
{
return specifier.RsiStateLike();

View File

@@ -1,5 +1,6 @@
using System;
using System.Runtime.InteropServices;
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace Robust.Shared.Maths
@@ -16,12 +17,12 @@ namespace Robust.Shared.Maths
/// <summary>
/// The X component of the Vector2i.
/// </summary>
public int X;
[JsonInclude] public int X;
/// <summary>
/// The Y component of the Vector2i.
/// </summary>
public int Y;
[JsonInclude] public int Y;
/// <summary>
/// Construct a vector from its coordinates.

View File

@@ -1,5 +1,6 @@
using System;
using System.Runtime.InteropServices;
using System.Text.Json.Serialization;
using Newtonsoft.Json;
namespace Robust.Shared.Maths
@@ -12,12 +13,12 @@ namespace Robust.Shared.Maths
/// <summary>
/// The X component of the Vector2i.
/// </summary>
public uint X;
[JsonInclude] public uint X;
/// <summary>
/// The Y component of the Vector2i.
/// </summary>
public uint Y;
[JsonInclude] public uint Y;
/// <summary>
/// Construct a vector from its coordinates.

View File

@@ -372,8 +372,9 @@ namespace Robust.Shared
// - Maximums
// Squared
// 35 m/s, AKA half a tile per frame allowed. Divide this by frametime to get units per second.
public static readonly CVarDef<float> MaxLinVelocity =
CVarDef.Create("physics.maxlinvelocity", 4.0f);
CVarDef.Create("physics.maxlinvelocity", 0.56f);
// Squared
public static readonly CVarDef<float> MaxAngVelocity =

View File

@@ -0,0 +1,47 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace Robust.Shared.Enums {
/// <summary>
/// Determines in which canvas layers an overlay gets drawn.
/// </summary>
[Flags]
public enum OverlaySpace {
/// <summary>
/// Used for matching bit flags.
/// </summary>
None = 0b000000,
/// <summary>
/// This overlay will be drawn in screen coordinates in the UI space above the world.
/// </summary>
ScreenSpace = 0b000001,
/// <summary>
/// This overlay will be drawn directly above normal worldspace, but a stencil equivalent to the FOV will be applied.
/// You likely want to use <see cref="WorldSpaceBelowFOV"/>. This space should only be used if you fully understand why you need this.
/// </summary>
WorldSpaceFOVStencil = 0b000010,
/// <summary>
/// This overlay will be drawn above entities, lighting, and FOV.
/// </summary>
WorldSpace = 0b000100,
/// <summary>
/// This overlay will be drawn beneath FOV; above lighting and entities.
/// </summary>
WorldSpaceBelowFOV = 0b001000,
/// <summary>
/// This overlay will be drawn beneath entities, lighting, and FOV; above grids.
/// </summary>
WorldSpaceBelowEntities = 0b010000,
/// <summary>
/// This overlay will be drawn in screen coordinates behind the world.
/// </summary>
ScreenSpaceBelowWorld = 0b100000,
}
}

View File

@@ -102,6 +102,7 @@ namespace Robust.Shared.GameObjects
if (_bodyType == value)
return;
var oldAnchored = _bodyType == BodyType.Static;
_bodyType = value;
ResetMassData();
@@ -120,8 +121,7 @@ namespace Robust.Shared.GameObjects
RegenerateContacts();
var oldAnchored = _bodyType == BodyType.Static;
var anchored = _bodyType == BodyType.Static;
var anchored = value == BodyType.Static;
if (oldAnchored != anchored)
{

View File

@@ -241,7 +241,7 @@ namespace Robust.Shared.GameObjects
{
if (!_parent.IsValid())
{
DebugTools.Assert("Tried to move root node.");
DebugTools.Assert("Parent is invalid while attempting to set WorldPosition - did you try to move root node?");
return;
}

View File

@@ -531,11 +531,13 @@ namespace Robust.Shared.Physics.Dynamics
var body = _stack[--stackCount];
_island.Add(body);
_islandSet.Add(body);
body.Awake = true;
// Static bodies don't propagate islands
if (body.BodyType == BodyType.Static) continue;
// As static bodies can never be awake (unlike Farseer) we'll set this after the check.
body.Awake = true;
for (var contactEdge = body.ContactEdges; contactEdge != null; contactEdge = contactEdge.Next)
{
var contact = contactEdge.Contact!;