mirror of
https://github.com/space-wizards/RobustToolbox.git
synced 2026-09-01 09:37:03 +02:00
388 lines
12 KiB
C#
388 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using Robust.Shared.GameObjects;
|
|
using Robust.Shared.Maths;
|
|
using Robust.Shared.Utility;
|
|
|
|
namespace Robust.Client.GameObjects;
|
|
|
|
public sealed partial class SpriteSystem
|
|
{
|
|
private readonly List<SpriteComponent.PostShaderEntry> _postShaderSortEntries = new();
|
|
private readonly List<TopologicalSort.GraphNode<SpriteComponent.PostShaderEntry>> _postShaderSortNodes = new();
|
|
private readonly Dictionary<string, TopologicalSort.GraphNode<SpriteComponent.PostShaderEntry>> _postShaderSortNodeById = new();
|
|
|
|
/// <summary>
|
|
/// Resets the sprite's animated layers to align with a given time (in seconds).
|
|
/// </summary>
|
|
public void SetAutoAnimateSync(SpriteComponent sprite, double time)
|
|
{
|
|
foreach (var layer in sprite.AllLayers)
|
|
{
|
|
if (layer is not SpriteComponent.Layer spriteLayer)
|
|
continue;
|
|
|
|
SetAutoAnimateSync(sprite, spriteLayer, time);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets the layer's animation to align with a given time (in seconds).
|
|
/// </summary>
|
|
public void SetAutoAnimateSync(SpriteComponent sprite, SpriteComponent.Layer layer, double time)
|
|
{
|
|
if (!layer.AutoAnimated)
|
|
return;
|
|
|
|
var rsi = layer.RSI ?? sprite.BaseRSI;
|
|
|
|
if (rsi == null || !rsi.TryGetState(layer.State, out var state))
|
|
{
|
|
state = GetFallbackState();
|
|
}
|
|
|
|
if (!state.IsAnimated)
|
|
{
|
|
return;
|
|
}
|
|
|
|
layer.AnimationTimeLeft = (float) -(time % state.TotalDelay);
|
|
layer.AnimationFrame = 0;
|
|
}
|
|
|
|
public void CopySprite(Entity<SpriteComponent?> source, Entity<SpriteComponent?> target)
|
|
{
|
|
if (!Resolve(source.Owner, ref source.Comp))
|
|
return;
|
|
|
|
if (!Resolve(target.Owner, ref target.Comp))
|
|
return;
|
|
|
|
target.Comp._baseRsi = source.Comp._baseRsi;
|
|
target.Comp._bounds = source.Comp._bounds;
|
|
target.Comp._visible = source.Comp._visible;
|
|
target.Comp.color = source.Comp.color;
|
|
target.Comp.offset = source.Comp.offset;
|
|
target.Comp.rotation = source.Comp.rotation;
|
|
target.Comp.scale = source.Comp.scale;
|
|
target.Comp.LocalMatrix = Matrix3Helpers.CreateTransform(
|
|
in target.Comp.offset,
|
|
in target.Comp.rotation,
|
|
in target
|
|
.Comp.scale);
|
|
|
|
target.Comp.drawDepth = source.Comp.drawDepth;
|
|
target.Comp.NoRotation = source.Comp.NoRotation;
|
|
target.Comp.DirectionOverride = source.Comp.DirectionOverride;
|
|
target.Comp.EnableDirectionOverride = source.Comp.EnableDirectionOverride;
|
|
target.Comp.Layers = new List<SpriteComponent.Layer>(source.Comp.Layers.Count);
|
|
foreach (var otherLayer in source.Comp.Layers)
|
|
{
|
|
var layer = new SpriteComponent.Layer(otherLayer, target.Comp);
|
|
layer.Index = target.Comp.Layers.Count;
|
|
layer.Owner = target!;
|
|
target.Comp.Layers.Add(layer);
|
|
}
|
|
|
|
target.Comp.IsInert = source.Comp.IsInert;
|
|
target.Comp.LayerMap = source.Comp.LayerMap.ShallowClone();
|
|
target.Comp.PostShaders = new List<SpriteComponent.PostShaderEntry>(source.Comp.PostShaders.Count);
|
|
foreach (var postShader in source.Comp.PostShaders)
|
|
{
|
|
target.Comp.PostShaders.Add(new SpriteComponent.PostShaderEntry(postShader));
|
|
}
|
|
|
|
target.Comp.PostShaderOrderDirty = source.Comp.PostShaderOrderDirty;
|
|
|
|
target.Comp.RenderOrder = source.Comp.RenderOrder;
|
|
target.Comp.GranularLayersRendering = source.Comp.GranularLayersRendering;
|
|
target.Comp.Loop = source.Comp.Loop;
|
|
|
|
DirtyBounds(target!);
|
|
_tree.QueueTreeUpdate(target!);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds a sprite to a queue that will update <see cref="SpriteComponent.IsInert"/> next frame.
|
|
/// </summary>
|
|
public void QueueUpdateIsInert(Entity<SpriteComponent> sprite)
|
|
{
|
|
if (sprite.Comp._inertUpdateQueued)
|
|
return;
|
|
|
|
sprite.Comp._inertUpdateQueued = true;
|
|
_inertUpdateQueue.Enqueue(sprite);
|
|
}
|
|
|
|
[Obsolete("Use QueueUpdateIsInert")]
|
|
public void QueueUpdateInert(EntityUid uid, SpriteComponent sprite) => QueueUpdateIsInert(new (uid, sprite));
|
|
|
|
/// <summary>
|
|
/// Gets the sprite's ordered post-shader list, resolving and sorting it if required.
|
|
/// </summary>
|
|
public IReadOnlyList<SpriteComponent.PostShaderEntry> GetPostShaders(Entity<SpriteComponent?> sprite)
|
|
{
|
|
if (!_query.Resolve(sprite.Owner, ref sprite.Comp, false))
|
|
return Array.Empty<SpriteComponent.PostShaderEntry>();
|
|
|
|
return GetPostShaders(sprite.Comp);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the sprite's ordered post-shader list, sorting it if required.
|
|
/// </summary>
|
|
public IReadOnlyList<SpriteComponent.PostShaderEntry> GetPostShaders(SpriteComponent sprite)
|
|
{
|
|
// Keep sorting lazy so repeatedly setting multiple shader properties during one update only pays once.
|
|
if (sprite.PostShaderOrderDirty)
|
|
SortPostShaders(sprite);
|
|
|
|
return sprite.PostShaders;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns whether the sprite has a post-shader with the given id.
|
|
/// </summary>
|
|
public bool HasPostShader(Entity<SpriteComponent?> sprite, string id)
|
|
{
|
|
if (!_query.Resolve(sprite.Owner, ref sprite.Comp, false))
|
|
return false;
|
|
|
|
return HasPostShader(sprite.Comp, id);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns whether the sprite has a post-shader with the given id.
|
|
/// </summary>
|
|
public bool HasPostShader(SpriteComponent sprite, string id)
|
|
{
|
|
// Could use sorted-dictionary but not expected to have a lot of these so this should be cheaper.
|
|
foreach (var shader in sprite.PostShaders)
|
|
{
|
|
if (shader.Id == id)
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to get a post-shader by id.
|
|
/// </summary>
|
|
public bool TryGetPostShader(Entity<SpriteComponent?> sprite, string id, out SpriteComponent.PostShaderEntry entry)
|
|
{
|
|
if (!_query.Resolve(sprite.Owner, ref sprite.Comp, false))
|
|
{
|
|
entry = default!;
|
|
return false;
|
|
}
|
|
|
|
return TryGetPostShader(sprite.Comp, id, out entry);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Tries to get a post-shader by id.
|
|
/// </summary>
|
|
public bool TryGetPostShader(SpriteComponent sprite, string id, out SpriteComponent.PostShaderEntry entry)
|
|
{
|
|
foreach (var shader in sprite.PostShaders)
|
|
{
|
|
if (shader.Id != id)
|
|
continue;
|
|
|
|
entry = shader;
|
|
return true;
|
|
}
|
|
|
|
entry = default!;
|
|
return false;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds or replaces a post-shader on the sprite.
|
|
/// </summary>
|
|
public void SetPostShader(
|
|
Entity<SpriteComponent?> sprite,
|
|
SpriteComponent.PostShaderArgs args)
|
|
{
|
|
if (!_query.Resolve(sprite.Owner, ref sprite.Comp, false))
|
|
return;
|
|
|
|
SetPostShader(sprite.Comp, args);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Adds or replaces a post-shader on the sprite.
|
|
/// </summary>
|
|
public void SetPostShader(
|
|
SpriteComponent sprite,
|
|
SpriteComponent.PostShaderArgs args)
|
|
{
|
|
var beforeArray = ToDependencyArray(args.Before);
|
|
var afterArray = ToDependencyArray(args.After);
|
|
|
|
// IDs are stable handles for systems to replace/remove their own post-shader without disturbing others.
|
|
for (var i = 0; i < sprite.PostShaders.Count; i++)
|
|
{
|
|
var existing = sprite.PostShaders[i];
|
|
if (existing.Id != args.Id)
|
|
continue;
|
|
|
|
existing.Prototype = default!;
|
|
existing.Shader = args.Shader;
|
|
existing.Mutable = args.Shader.Mutable;
|
|
existing.GetScreenTexture = args.GetScreenTexture;
|
|
existing.RaiseShaderEvent = args.RaiseShaderEvent;
|
|
existing.Before = beforeArray;
|
|
existing.After = afterArray;
|
|
sprite.PostShaderOrderDirty = true;
|
|
return;
|
|
}
|
|
|
|
sprite.PostShaders.Add(new SpriteComponent.PostShaderEntry(
|
|
args.Id,
|
|
args.Shader,
|
|
args.GetScreenTexture,
|
|
args.RaiseShaderEvent,
|
|
beforeArray,
|
|
afterArray)
|
|
{
|
|
InsertionIndex = sprite.PostShaders.Count,
|
|
});
|
|
sprite.PostShaderOrderDirty = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a post-shader from the sprite by id.
|
|
/// </summary>
|
|
public void RemovePostShader(Entity<SpriteComponent?> sprite, string id)
|
|
{
|
|
if (!_query.Resolve(sprite.Owner, ref sprite.Comp, false))
|
|
return;
|
|
|
|
RemovePostShader(sprite.Comp, id);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes a post-shader from the sprite by id.
|
|
/// </summary>
|
|
public void RemovePostShader(SpriteComponent sprite, string id)
|
|
{
|
|
for (var i = 0; i < sprite.PostShaders.Count; i++)
|
|
{
|
|
if (sprite.PostShaders[i].Id != id)
|
|
continue;
|
|
|
|
sprite.PostShaders.RemoveAt(i);
|
|
sprite.PostShaderOrderDirty = true;
|
|
return;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes all post-shaders from the sprite.
|
|
/// </summary>
|
|
public void ClearPostShaders(Entity<SpriteComponent?> sprite)
|
|
{
|
|
if (!_query.Resolve(sprite.Owner, ref sprite.Comp, false))
|
|
return;
|
|
|
|
ClearPostShaders(sprite.Comp);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Removes all post-shaders from the sprite.
|
|
/// </summary>
|
|
public void ClearPostShaders(SpriteComponent sprite)
|
|
{
|
|
sprite.PostShaders.Clear();
|
|
sprite.PostShaderOrderDirty = false;
|
|
}
|
|
|
|
private void SortPostShaders(SpriteComponent sprite)
|
|
{
|
|
sprite.PostShaderOrderDirty = false;
|
|
|
|
var shaders = sprite.PostShaders;
|
|
if (shaders.Count < 2)
|
|
return;
|
|
|
|
_postShaderSortEntries.Clear();
|
|
_postShaderSortNodes.Clear();
|
|
_postShaderSortNodeById.Clear();
|
|
|
|
foreach (var shader in shaders)
|
|
{
|
|
_postShaderSortEntries.Add(shader);
|
|
}
|
|
|
|
_postShaderSortEntries.Sort(PostShaderInsertionComparison);
|
|
|
|
foreach (var shader in _postShaderSortEntries)
|
|
{
|
|
var node = new TopologicalSort.GraphNode<SpriteComponent.PostShaderEntry>(shader);
|
|
_postShaderSortNodes.Add(node);
|
|
_postShaderSortNodeById[shader.Id] = node;
|
|
}
|
|
|
|
foreach (var node in _postShaderSortNodes)
|
|
{
|
|
foreach (var before in node.Value.Before)
|
|
{
|
|
if (_postShaderSortNodeById.TryGetValue(before, out var dependency))
|
|
AddPostShaderDependency(node, dependency);
|
|
}
|
|
|
|
foreach (var after in node.Value.After)
|
|
{
|
|
if (_postShaderSortNodeById.TryGetValue(after, out var dependency))
|
|
AddPostShaderDependency(dependency, node);
|
|
}
|
|
}
|
|
|
|
try
|
|
{
|
|
var sorted = TopologicalSort.Sort(_postShaderSortNodes).ToArray();
|
|
|
|
shaders.Clear();
|
|
foreach (var shader in sorted)
|
|
{
|
|
shaders.Add(shader);
|
|
}
|
|
}
|
|
catch (InvalidOperationException)
|
|
{
|
|
Log.Warning("Post-shader dependency cycle detected on sprite {0}; keeping insertion order.", sprite.Owner);
|
|
shaders.Sort(PostShaderInsertionComparison);
|
|
}
|
|
}
|
|
|
|
private static void AddPostShaderDependency(
|
|
TopologicalSort.GraphNode<SpriteComponent.PostShaderEntry> before,
|
|
TopologicalSort.GraphNode<SpriteComponent.PostShaderEntry> after)
|
|
{
|
|
if (before == after || before.Dependant.Contains(after))
|
|
return;
|
|
|
|
before.Dependant.Add(after);
|
|
}
|
|
|
|
private static int PostShaderInsertionComparison(SpriteComponent.PostShaderEntry x, SpriteComponent.PostShaderEntry y)
|
|
{
|
|
var cmp = x.InsertionIndex.CompareTo(y.InsertionIndex);
|
|
return cmp != 0 ? cmp : string.CompareOrdinal(x.Id, y.Id);
|
|
}
|
|
|
|
private static string[] ToDependencyArray(IEnumerable<string>? dependencies)
|
|
{
|
|
if (dependencies == null)
|
|
return Array.Empty<string>();
|
|
|
|
if (dependencies is string[] array)
|
|
return array;
|
|
|
|
return new List<string>(dependencies).ToArray();
|
|
}
|
|
}
|