Skip to content

Commit

Permalink
Texture fade overlay
Browse files Browse the repository at this point in the history
  • Loading branch information
stalengd committed Aug 12, 2024
1 parent 2d37e46 commit 1c59450
Show file tree
Hide file tree
Showing 8 changed files with 348 additions and 0 deletions.
119 changes: 119 additions & 0 deletions Content.Client/SS220/Overlays/OverlayStack.cs
Original file line number Diff line number Diff line change
@@ -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
{
/// <summary>
/// Special overlays container created to bypass limits of <see cref="IOverlayManager"/> not supporting multiple overlays of the same type.
/// </summary>
public sealed class OverlayStack : Overlay
{
public override OverlaySpace Space => _space;
private readonly SortedDictionary<StackableOverlay, OverlayData> _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<OverlayStack>(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<Overlay>
{
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);
}
}
}

/// <summary>
/// An overlay supported by <see cref="OverlayStack"/>
/// </summary>
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);
}
}
}
75 changes: 75 additions & 0 deletions Content.Client/SS220/TextureFade/TextureFadeOverlay.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Performs alpha clip on the privided texture with variable threshold (threshold filter). See <see cref="TextureFadeOverlayComponent"/> for more automatic use.
/// </summary>
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<SpriteSystem>();
_shader = _prototypeManager.Index<ShaderPrototype>("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;
}
}
36 changes: 36 additions & 0 deletions Content.Client/SS220/TextureFade/TextureFadeOverlayComponent.cs
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Component for automatic texture fade processing, you can still use <see cref="TextureFadeOverlay"/> directly.
/// You can use all this data fiels directly in code to enable/disable, set progress, etc.
/// </summary>
[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;
}
82 changes: 82 additions & 0 deletions Content.Client/SS220/TextureFade/TextureFadeOverlaySystem.cs
Original file line number Diff line number Diff line change
@@ -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<TextureFadeOverlayComponent, ComponentRemove>(OnRemove);
}

public override void FrameUpdate(float frameTime)
{
var componentsQuery = EntityQueryEnumerator<TextureFadeOverlayComponent>();
while (componentsQuery.MoveNext(out var comp))
{
HandleOverlayActivityUpdate(comp);
HandleOverlayProgressUpdate(comp, frameTime);
}
}

private void OnRemove(Entity<TextureFadeOverlayComponent> 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;
}
}
Original file line number Diff line number Diff line change
@@ -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
{
}
Original file line number Diff line number Diff line change
@@ -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;

/// <summary>
/// Component for automatic texture fade processing.
/// Makes sense only on client.
/// </summary>
public abstract partial class SharedTextureFadeOverlayComponent : Component
{
}
5 changes: 5 additions & 0 deletions Resources/Prototypes/SS220/Shaders/texture_fade.yml
Original file line number Diff line number Diff line change
@@ -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"
12 changes: 12 additions & 0 deletions Resources/Textures/SS220/Shaders/texture_fade.swsl
Original file line number Diff line number Diff line change
@@ -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;
}

0 comments on commit 1c59450

Please sign in to comment.