2021-06-24 04:48:11 +02:00
using Content.Shared.Singularity.Components ;
2021-06-09 22:19:39 +02:00
using Robust.Client.Graphics ;
2023-08-12 16:43:07 -07:00
using Robust.Client.UserInterface.CustomControls ;
2021-03-09 04:33:41 -06:00
using Robust.Shared.Enums ;
2021-06-09 22:19:39 +02:00
using Robust.Shared.Prototypes ;
2023-08-12 16:43:07 -07:00
using System.Numerics ;
2021-03-09 04:33:41 -06:00
2021-06-09 22:19:39 +02:00
namespace Content.Client.Singularity
2021-03-09 04:33:41 -06:00
{
2023-08-12 16:43:07 -07:00
public sealed class SingularityOverlay : Overlay , IEntityEventSubscriber
2021-03-09 04:33:41 -06:00
{
2022-04-29 00:43:16 +12:00
[Dependency] private readonly IEntityManager _entMan = default ! ;
2021-03-09 04:33:41 -06:00
[Dependency] private readonly IPrototypeManager _prototypeManager = default ! ;
2023-08-12 16:43:07 -07:00
private SharedTransformSystem ? _xformSystem = null ;
2021-06-24 04:48:11 +02:00
2022-04-29 00:43:16 +12:00
/// <summary>
/// Maximum number of distortions that can be shown on screen at a time.
/// If this value is changed, the shader itself also needs to be updated.
/// </summary>
public const int MaxCount = 5 ;
private const float MaxDistance = 20f ;
2021-03-09 04:33:41 -06:00
public override OverlaySpace Space = > OverlaySpace . WorldSpace ;
public override bool RequestScreenTexture = > true ;
private readonly ShaderInstance _shader ;
public SingularityOverlay ( )
{
IoCManager . InjectDependencies ( this ) ;
_shader = _prototypeManager . Index < ShaderPrototype > ( "Singularity" ) . Instance ( ) . Duplicate ( ) ;
2022-06-20 12:16:32 +12:00
_shader . SetParameter ( "maxDistance" , MaxDistance * EyeManager . PixelsPerMeter ) ;
2023-08-12 16:43:07 -07:00
_entMan . EventBus . SubscribeEvent < PixelToMapEvent > ( EventSource . Local , this , OnProjectFromScreenToMap ) ;
ZIndex = 101 ; // Should be drawn after the placement overlay so admins placing items near the singularity can tell where they're going.
2021-03-09 04:33:41 -06:00
}
2023-07-08 14:08:32 +10:00
private readonly Vector2 [ ] _positions = new Vector2 [ MaxCount ] ;
private readonly float [ ] _intensities = new float [ MaxCount ] ;
private readonly float [ ] _falloffPowers = new float [ MaxCount ] ;
2022-06-20 12:16:32 +12:00
private int _count = 0 ;
2021-03-09 04:33:41 -06:00
2022-06-20 12:16:32 +12:00
protected override bool BeforeDraw ( in OverlayDrawArgs args )
{
if ( args . Viewport . Eye = = null )
return false ;
2023-08-12 16:43:07 -07:00
if ( _xformSystem is null & & ! _entMan . TrySystem ( out _xformSystem ) )
return false ;
2021-03-09 04:33:41 -06:00
2022-06-20 12:16:32 +12:00
_count = 0 ;
2023-08-12 16:43:07 -07:00
var query = _entMan . EntityQueryEnumerator < SingularityDistortionComponent , TransformComponent > ( ) ;
while ( query . MoveNext ( out var uid , out var distortion , out var xform ) )
2021-03-09 04:33:41 -06:00
{
2022-06-20 12:16:32 +12:00
if ( xform . MapID ! = args . MapId )
2022-04-29 00:43:16 +12:00
continue ;
2021-06-24 04:48:11 +02:00
2023-08-12 16:43:07 -07:00
var mapPos = _xformSystem . GetWorldPosition ( uid ) ;
2022-06-20 12:16:32 +12:00
2022-04-29 00:43:16 +12:00
// is the distortion in range?
2023-07-08 14:08:32 +10:00
if ( ( mapPos - args . WorldAABB . ClosestPoint ( mapPos ) ) . LengthSquared ( ) > MaxDistance * MaxDistance )
2022-04-29 00:43:16 +12:00
continue ;
2021-03-09 04:33:41 -06:00
2022-04-29 00:43:16 +12:00
// To be clear, this needs to use "inside-viewport" pixels.
// In other words, specifically NOT IViewportControl.WorldToScreen (which uses outer coordinates).
2022-06-20 12:16:32 +12:00
var tempCoords = args . Viewport . WorldToLocal ( mapPos ) ;
2023-08-12 16:43:07 -07:00
tempCoords . Y = args . Viewport . Size . Y - tempCoords . Y ; // Local space to fragment space.
2021-03-09 04:33:41 -06:00
2022-06-20 12:16:32 +12:00
_positions [ _count ] = tempCoords ;
_intensities [ _count ] = distortion . Intensity ;
_falloffPowers [ _count ] = distortion . FalloffPower ;
_count + + ;
2021-03-09 04:33:41 -06:00
2022-06-20 12:16:32 +12:00
if ( _count = = MaxCount )
2022-04-29 00:43:16 +12:00
break ;
}
2021-03-09 04:33:41 -06:00
2022-06-20 12:16:32 +12:00
return ( _count > 0 ) ;
}
protected override void Draw ( in OverlayDrawArgs args )
{
if ( ScreenTexture = = null | | args . Viewport . Eye = = null )
2022-04-29 00:43:16 +12:00
return ;
2021-06-24 04:48:11 +02:00
2023-07-12 22:12:24 +03:00
_shader ? . SetParameter ( "renderScale" , args . Viewport . RenderScale * args . Viewport . Eye . Scale ) ;
2022-06-20 12:16:32 +12:00
_shader ? . SetParameter ( "count" , _count ) ;
_shader ? . SetParameter ( "position" , _positions ) ;
_shader ? . SetParameter ( "intensity" , _intensities ) ;
_shader ? . SetParameter ( "falloffPower" , _falloffPowers ) ;
2022-04-29 00:43:16 +12:00
_shader ? . SetParameter ( "SCREEN_TEXTURE" , ScreenTexture ) ;
var worldHandle = args . WorldHandle ;
worldHandle . UseShader ( _shader ) ;
worldHandle . DrawRect ( args . WorldAABB , Color . White ) ;
worldHandle . UseShader ( null ) ;
2021-03-09 04:33:41 -06:00
}
2023-08-12 16:43:07 -07:00
/// <summary>
/// Repeats the transformation applied by the shader in <see cref="Resources/Textures/Shaders/singularity.swsl"/>
/// </summary>
private void OnProjectFromScreenToMap ( ref PixelToMapEvent args )
{ // Mostly copypasta from the singularity shader.
var maxDistance = MaxDistance * EyeManager . PixelsPerMeter ;
var finalCoords = args . VisiblePosition ;
for ( var i = 0 ; i < MaxCount & & i < _count ; i + + )
{
// An explanation of pain:
// The shader used by the singularity to create the neat distortion effect occurs in _fragment space_
// All of these calculations are done in _local space_.
// The only difference between the two is that in fragment space 'Y' is measured in pixels from the bottom of the viewport...
// and in local space 'Y' is measured in pixels from the top of the viewport.
// As a minor optimization the locations of the singularities are transformed into fragment space in BeforeDraw so the shader doesn't need to.
// We need to undo that here or this will transform the cursor position as if the singularities were mirrored vertically relative to the center of the viewport.
var localPosition = _positions [ i ] ;
localPosition . Y = args . Viewport . Size . Y - localPosition . Y ;
var delta = args . VisiblePosition - localPosition ;
var distance = ( delta / args . Viewport . RenderScale ) . Length ( ) ;
var deformation = _intensities [ i ] / MathF . Pow ( distance , _falloffPowers [ i ] ) ;
// ensure deformation goes to zero at max distance
// avoids long-range single-pixel shifts that are noticeable when leaving PVS.
if ( distance > = maxDistance )
deformation = 0.0f ;
else
deformation * = 1.0f - MathF . Pow ( distance / maxDistance , 4.0f ) ;
if ( deformation > 0.8 )
deformation = MathF . Pow ( deformation , 0.3f ) ;
finalCoords - = delta * deformation ;
}
finalCoords . X - = MathF . Floor ( finalCoords . X / ( args . Viewport . Size . X * 2 ) ) * args . Viewport . Size . X * 2 ; // Manually handle the wrapping reflection behaviour used by the viewport texture.
finalCoords . Y - = MathF . Floor ( finalCoords . Y / ( args . Viewport . Size . Y * 2 ) ) * args . Viewport . Size . Y * 2 ;
finalCoords . X = ( finalCoords . X > = args . Viewport . Size . X ) ? ( ( args . Viewport . Size . X * 2 ) - finalCoords . X ) : finalCoords . X ;
finalCoords . Y = ( finalCoords . Y > = args . Viewport . Size . Y ) ? ( ( args . Viewport . Size . Y * 2 ) - finalCoords . Y ) : finalCoords . Y ;
args . VisiblePosition = finalCoords ;
}
2021-03-09 04:33:41 -06:00
}
}