Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Texture fade overlay #1580

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
}
Loading