From 1c594509dba986a9386ebe2c425362e4d0daa8e6 Mon Sep 17 00:00:00 2001 From: stalengd Date: Tue, 13 Aug 2024 00:44:39 +0300 Subject: [PATCH] Texture fade overlay --- Content.Client/SS220/Overlays/OverlayStack.cs | 119 ++++++++++++++++++ .../SS220/TextureFade/TextureFadeOverlay.cs | 75 +++++++++++ .../TextureFadeOverlayComponent.cs | 36 ++++++ .../TextureFade/TextureFadeOverlaySystem.cs | 82 ++++++++++++ .../TextureFadeOverlayComponent.cs | 9 ++ .../SharedTextureFadeOverlayComponent.cs | 10 ++ .../Prototypes/SS220/Shaders/texture_fade.yml | 5 + .../Textures/SS220/Shaders/texture_fade.swsl | 12 ++ 8 files changed, 348 insertions(+) create mode 100644 Content.Client/SS220/Overlays/OverlayStack.cs create mode 100644 Content.Client/SS220/TextureFade/TextureFadeOverlay.cs create mode 100644 Content.Client/SS220/TextureFade/TextureFadeOverlayComponent.cs create mode 100644 Content.Client/SS220/TextureFade/TextureFadeOverlaySystem.cs create mode 100644 Content.Server/SS220/TextureFade/TextureFadeOverlayComponent.cs create mode 100644 Content.Shared/SS220/TextureFade/SharedTextureFadeOverlayComponent.cs create mode 100644 Resources/Prototypes/SS220/Shaders/texture_fade.yml create mode 100644 Resources/Textures/SS220/Shaders/texture_fade.swsl diff --git a/Content.Client/SS220/Overlays/OverlayStack.cs b/Content.Client/SS220/Overlays/OverlayStack.cs new file mode 100644 index 00000000000000..80d2433542836c --- /dev/null +++ b/Content.Client/SS220/Overlays/OverlayStack.cs @@ -0,0 +1,119 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt +using Robust.Client.Graphics; +using Robust.Shared.Enums; +using Robust.Shared.Timing; + +namespace Content.Client.SS220.Overlays +{ + /// + /// Special overlays container created to bypass limits of not supporting multiple overlays of the same type. + /// + public sealed class OverlayStack : Overlay + { + public override OverlaySpace Space => _space; + private readonly SortedDictionary _overlays = new(OverlayComparer.Instance); + + private OverlaySpace _space; + + public bool AddOverlay(StackableOverlay overlay) + { + if (_overlays.ContainsKey(overlay)) + return false; + _overlays.Add(overlay, new()); + _space |= overlay.Space; + return true; + } + + public bool RemoveOverlay(StackableOverlay overlay) + { + if (!_overlays.ContainsKey(overlay)) + return false; + _overlays.Remove(overlay); + return true; + } + + protected override void FrameUpdate(FrameEventArgs args) + { + foreach (var (overlay, _) in _overlays) + { + overlay.FrameUpdatePublic(args); + } + } + + protected override bool BeforeDraw(in OverlayDrawArgs args) + { + var result = false; + foreach (var (overlay, data) in _overlays) + { + if (!ShouldDraw(overlay, args)) + continue; + var shouldDraw = overlay.BeforeDrawPublic(in args); + data.ShouldDrawThisFrame = shouldDraw; + result |= shouldDraw; + } + return result; + } + + protected override void Draw(in OverlayDrawArgs args) + { + foreach (var (overlay, data) in _overlays) + { + if (!ShouldDraw(overlay, args) || !data.ShouldDrawThisFrame) + continue; + overlay.DrawPublic(in args); + } + } + + public static OverlayStack Get(IOverlayManager overlayManager) + { + if (overlayManager.TryGetOverlay(out var overlay)) + return overlay; + overlay = new OverlayStack(); + overlayManager.AddOverlay(overlay); + return overlay; + } + + private static bool ShouldDraw(StackableOverlay overlay, OverlayDrawArgs drawArgs) + { + return (overlay.Space & drawArgs.Space) != 0; + } + + private sealed class OverlayData + { + public bool ShouldDrawThisFrame; + } + + private sealed class OverlayComparer : IComparer + { + public static readonly OverlayComparer Instance = new(); + + public int Compare(Overlay? x, Overlay? y) + { + var zX = x?.ZIndex ?? 0; + var zY = y?.ZIndex ?? 0; + return zX.CompareTo(zY); + } + } + } + + /// + /// An overlay supported by + /// + public abstract class StackableOverlay : Overlay + { + public void FrameUpdatePublic(FrameEventArgs args) + { + FrameUpdate(args); + } + + public bool BeforeDrawPublic(in OverlayDrawArgs args) + { + return BeforeDraw(in args); + } + + public void DrawPublic(in OverlayDrawArgs args) + { + Draw(in args); + } + } +} diff --git a/Content.Client/SS220/TextureFade/TextureFadeOverlay.cs b/Content.Client/SS220/TextureFade/TextureFadeOverlay.cs new file mode 100644 index 00000000000000..e0be72beb9762c --- /dev/null +++ b/Content.Client/SS220/TextureFade/TextureFadeOverlay.cs @@ -0,0 +1,75 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt +using System.Numerics; +using Content.Client.SS220.Overlays; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Shared.Enums; +using Robust.Shared.Prototypes; +using Robust.Shared.Timing; +using Robust.Shared.Utility; + +namespace Content.Client.SS220.TextureFade; + +/// +/// Performs alpha clip on the privided texture with variable threshold (threshold filter). See for more automatic use. +/// +public sealed class TextureFadeOverlay : StackableOverlay +{ + [Dependency] private readonly IPrototypeManager _prototypeManager = default!; + [Dependency] private readonly IEntityManager _entityManager = default!; + private readonly SpriteSystem _spriteSystem = default!; + + public override OverlaySpace Space => OverlaySpace.WorldSpace; + public SpriteSpecifier? Sprite; + public Color Modulate = Color.White; + public float FadeProgress = 0f; + public TimeSpan Time; + public bool Loop = true; + + private readonly ShaderInstance _shader; + + public TextureFadeOverlay() + { + IoCManager.InjectDependencies(this); + _spriteSystem = _entityManager.EntitySysManager.GetEntitySystem(); + _shader = _prototypeManager.Index("TextureFade").InstanceUnique(); + } + + protected override void FrameUpdate(FrameEventArgs args) + { + Time += TimeSpan.FromSeconds(args.DeltaSeconds); + } + + protected override void Draw(in OverlayDrawArgs args) + { + if (Sprite == null) + return; + + var texture = _spriteSystem.GetFrame(Sprite, Time, Loop); + + var worldHandle = args.WorldHandle; + _shader.SetParameter("FadeProgress", FadeProgress); + + var viewQuad = args.WorldBounds; + var viewSize = viewQuad.Box.Size; + var viewRatio = viewSize.X / viewSize.Y; + var regionSize = texture.Size; + var regionRatio = (float)regionSize.X / regionSize.Y; + var scaleBy = Vector2.One; + if (viewRatio > regionRatio) + { + scaleBy.Y = viewRatio / regionRatio; + } + else + { + scaleBy.X = regionRatio / viewRatio; + } + viewQuad.Box = viewQuad.Box.Scale(scaleBy); + + worldHandle.Modulate = Modulate; + worldHandle.UseShader(_shader); + worldHandle.DrawTextureRectRegion(texture, viewQuad); + worldHandle.UseShader(null); + worldHandle.Modulate = Color.White; + } +} diff --git a/Content.Client/SS220/TextureFade/TextureFadeOverlayComponent.cs b/Content.Client/SS220/TextureFade/TextureFadeOverlayComponent.cs new file mode 100644 index 00000000000000..52c1642e0286bd --- /dev/null +++ b/Content.Client/SS220/TextureFade/TextureFadeOverlayComponent.cs @@ -0,0 +1,36 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt +using Content.Shared.SS220.TextureFade; +using Robust.Shared.Utility; + +namespace Content.Client.SS220.TextureFade; + +/// +/// Component for automatic texture fade processing, you can still use directly. +/// You can use all this data fiels directly in code to enable/disable, set progress, etc. +/// +[RegisterComponent] +public sealed partial class TextureFadeOverlayComponent : SharedTextureFadeOverlayComponent +{ + [DataField, ViewVariables(VVAccess.ReadWrite)] + public bool IsEnabled = false; + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float FadeProgress = 1; + [DataField, ViewVariables(VVAccess.ReadWrite)] + public SpriteSpecifier Sprite = SpriteSpecifier.Invalid; + [DataField, ViewVariables(VVAccess.ReadWrite)] + public Color Modulate = Color.White; + [DataField, ViewVariables(VVAccess.ReadWrite)] + public int ZIndex = 0; + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float ProgressSpeed = 0; + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float MinProgress = 0; + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float MaxProgress = 1; + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float PulseMagnitude = 0; + [DataField, ViewVariables(VVAccess.ReadWrite)] + public float PulseRate = 0; + + public TextureFadeOverlay? Overlay; +} diff --git a/Content.Client/SS220/TextureFade/TextureFadeOverlaySystem.cs b/Content.Client/SS220/TextureFade/TextureFadeOverlaySystem.cs new file mode 100644 index 00000000000000..166a9c5a94ca59 --- /dev/null +++ b/Content.Client/SS220/TextureFade/TextureFadeOverlaySystem.cs @@ -0,0 +1,82 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt +using Content.Client.SS220.Overlays; +using Robust.Client.Graphics; + +namespace Content.Client.SS220.TextureFade; + +public sealed class TextureFadeOverlaySystem : EntitySystem +{ + [Dependency] private readonly IOverlayManager _overlayManager = default!; + + public override void Initialize() + { + SubscribeLocalEvent(OnRemove); + } + + public override void FrameUpdate(float frameTime) + { + var componentsQuery = EntityQueryEnumerator(); + while (componentsQuery.MoveNext(out var comp)) + { + HandleOverlayActivityUpdate(comp); + HandleOverlayProgressUpdate(comp, frameTime); + } + } + + private void OnRemove(Entity entity, ref ComponentRemove args) + { + DestroyOverlay(entity.Comp); + } + + private void HandleOverlayActivityUpdate(TextureFadeOverlayComponent component) + { + if (component.IsEnabled && component.Overlay is null) + { + component.Overlay = CreateOverlay(component); + return; + } + if (!component.IsEnabled && component.Overlay is { }) + { + DestroyOverlay(component); + return; + } + } + + private void HandleOverlayProgressUpdate(TextureFadeOverlayComponent component, float frameTime) + { + if (component.Overlay == null) + return; + if (component.ProgressSpeed != 0f) + { + component.FadeProgress += component.ProgressSpeed * frameTime; + component.FadeProgress = Math.Clamp(component.FadeProgress, component.MinProgress, component.MaxProgress); + } + var fadeProgressMod = component.FadeProgress; + fadeProgressMod += (float)Math.Sin(Math.PI * component.Overlay.Time.TotalSeconds * component.PulseRate) * component.PulseMagnitude; + fadeProgressMod = Math.Clamp(fadeProgressMod, 0f, 1f); + component.Overlay.FadeProgress = fadeProgressMod; + component.Overlay.Modulate = component.Modulate; + component.Overlay.ZIndex = component.ZIndex; + } + + private TextureFadeOverlay CreateOverlay(TextureFadeOverlayComponent component) + { + var overlay = new TextureFadeOverlay() + { + Sprite = component.Sprite, + Modulate = component.Modulate, + ZIndex = component.ZIndex, + }; + OverlayStack.Get(_overlayManager).AddOverlay(overlay); + return overlay; + } + + private void DestroyOverlay(TextureFadeOverlayComponent component) + { + if (component.Overlay is null) + return; + OverlayStack.Get(_overlayManager).RemoveOverlay(component.Overlay); + component.Overlay.Dispose(); + component.Overlay = null; + } +} diff --git a/Content.Server/SS220/TextureFade/TextureFadeOverlayComponent.cs b/Content.Server/SS220/TextureFade/TextureFadeOverlayComponent.cs new file mode 100644 index 00000000000000..8b0b48d9ab93dc --- /dev/null +++ b/Content.Server/SS220/TextureFade/TextureFadeOverlayComponent.cs @@ -0,0 +1,9 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt +using Content.Shared.SS220.TextureFade; + +namespace Content.Server.SS220.TextureFade; + +[RegisterComponent] +public sealed partial class TextureFadeOverlayComponent : SharedTextureFadeOverlayComponent +{ +} diff --git a/Content.Shared/SS220/TextureFade/SharedTextureFadeOverlayComponent.cs b/Content.Shared/SS220/TextureFade/SharedTextureFadeOverlayComponent.cs new file mode 100644 index 00000000000000..595f847e86e73a --- /dev/null +++ b/Content.Shared/SS220/TextureFade/SharedTextureFadeOverlayComponent.cs @@ -0,0 +1,10 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt +namespace Content.Shared.SS220.TextureFade; + +/// +/// Component for automatic texture fade processing. +/// Makes sense only on client. +/// +public abstract partial class SharedTextureFadeOverlayComponent : Component +{ +} diff --git a/Resources/Prototypes/SS220/Shaders/texture_fade.yml b/Resources/Prototypes/SS220/Shaders/texture_fade.yml new file mode 100644 index 00000000000000..8b2bc8ebd7fcf7 --- /dev/null +++ b/Resources/Prototypes/SS220/Shaders/texture_fade.yml @@ -0,0 +1,5 @@ +# © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt +- type: shader + id: TextureFade + kind: source + path: "/Textures/SS220/Shaders/texture_fade.swsl" \ No newline at end of file diff --git a/Resources/Textures/SS220/Shaders/texture_fade.swsl b/Resources/Textures/SS220/Shaders/texture_fade.swsl new file mode 100644 index 00000000000000..af5ad221f9f7e1 --- /dev/null +++ b/Resources/Textures/SS220/Shaders/texture_fade.swsl @@ -0,0 +1,12 @@ +// © SS220, An EULA/CLA with a hosting restriction, full text: https://raw.githubusercontent.com/SerbiaStrong-220/space-station-14/master/CLA.txt +uniform highp float FadeProgress; + +void fragment() { + + highp vec4 color = texture2D(TEXTURE, UV.xy); + if (color.a <= FadeProgress) { + discard; + } + color.a = 1; + COLOR = color; +} \ No newline at end of file