2022-01-07 18:54:06 +01:00
using System ;
using System.Collections.Generic ;
2022-03-01 18:23:28 -06:00
using Robust.Client.GameObjects ;
using Robust.Client.Graphics ;
2022-01-07 18:54:06 +01:00
using Robust.Client.ResourceManagement ;
2023-11-27 22:12:34 +11:00
using Robust.Shared.ContentPack ;
2022-01-07 18:54:06 +01:00
using Robust.Shared.GameObjects ;
using Robust.Shared.Timing ;
using SixLabors.ImageSharp ;
using SixLabors.ImageSharp.PixelFormats ;
using SixLabors.ImageSharp.Processing ;
using static Robust . UnitTesting . RobustIntegrationTest ;
namespace Content.MapRenderer.Painters ;
2022-02-16 00:23:23 -07:00
public sealed class EntityPainter
2022-01-07 18:54:06 +01:00
{
2023-11-27 22:12:34 +11:00
private readonly IResourceManager _resManager ;
2022-01-07 18:54:06 +01:00
private readonly Dictionary < ( string path , string state ) , Image > _images ;
private readonly Image _errorImage ;
private readonly IEntityManager _sEntityManager ;
public EntityPainter ( ClientIntegrationInstance client , ServerIntegrationInstance server )
{
2023-11-27 22:12:34 +11:00
_resManager = client . ResolveDependency < IResourceManager > ( ) ;
2022-01-07 18:54:06 +01:00
_sEntityManager = server . ResolveDependency < IEntityManager > ( ) ;
_images = new Dictionary < ( string path , string state ) , Image > ( ) ;
2023-11-27 22:12:34 +11:00
_errorImage = Image . Load < Rgba32 > ( _resManager . ContentFileRead ( "/Textures/error.rsi/error.png" ) ) ;
2022-01-07 18:54:06 +01:00
}
public void Run ( Image canvas , List < EntityData > entities )
{
var stopwatch = new Stopwatch ( ) ;
stopwatch . Start ( ) ;
// TODO cache this shit what are we insane
entities . Sort ( Comparer < EntityData > . Create ( ( x , y ) = > x . Sprite . DrawDepth . CompareTo ( y . Sprite . DrawDepth ) ) ) ;
2023-06-26 01:56:20 +10:00
var xformSystem = _sEntityManager . System < SharedTransformSystem > ( ) ;
2022-01-07 18:54:06 +01:00
foreach ( var entity in entities )
{
2023-06-26 01:56:20 +10:00
Run ( canvas , entity , xformSystem ) ;
2022-01-07 18:54:06 +01:00
}
2022-02-23 09:23:39 -07:00
Console . WriteLine ( $"{nameof(EntityPainter)} painted {entities.Count} entities in {(int) stopwatch.Elapsed.TotalMilliseconds} ms" ) ;
2022-01-07 18:54:06 +01:00
}
2023-06-26 01:56:20 +10:00
public void Run ( Image canvas , EntityData entity , SharedTransformSystem xformSystem )
2022-01-07 18:54:06 +01:00
{
if ( ! entity . Sprite . Visible | | entity . Sprite . ContainerOccluded )
{
return ;
}
2023-06-26 01:56:20 +10:00
var worldRotation = xformSystem . GetWorldRotation ( entity . Owner ) ;
2022-01-07 18:54:06 +01:00
foreach ( var layer in entity . Sprite . AllLayers )
{
if ( ! layer . Visible )
{
continue ;
}
if ( ! layer . RsiState . IsValid )
{
continue ;
}
var rsi = layer . ActualRsi ;
Image image ;
2023-06-26 01:56:20 +10:00
if ( rsi = = null | | ! rsi . TryGetState ( layer . RsiState , out var state ) )
2022-01-07 18:54:06 +01:00
{
image = _errorImage ;
}
else
{
var key = ( rsi . Path ! . ToString ( ) , state . StateId . Name ! ) ;
if ( ! _images . TryGetValue ( key , out image ! ) )
{
2023-11-27 22:12:34 +11:00
var stream = _resManager . ContentFileRead ( $"{rsi.Path}/{state.StateId}.png" ) ;
2022-01-07 18:54:06 +01:00
image = Image . Load < Rgba32 > ( stream ) ;
_images [ key ] = image ;
}
}
image = image . CloneAs < Rgba32 > ( ) ;
2023-06-26 01:56:20 +10:00
static ( int , int , int , int ) GetRsiFrame ( RSI ? rsi , Image image , EntityData entity , ISpriteLayer layer , int direction )
2022-01-07 18:54:06 +01:00
{
2022-03-01 18:23:28 -06:00
if ( rsi is null )
return ( 0 , 0 , EyeManager . PixelsPerMeter , EyeManager . PixelsPerMeter ) ;
var statesX = image . Width / rsi . Size . X ;
var statesY = image . Height / rsi . Size . Y ;
var stateCount = statesX * statesY ;
var frames = stateCount / entity . Sprite . GetLayerDirectionCount ( layer ) ;
var target = direction * frames ;
var targetY = target / statesX ;
var targetX = target % statesY ;
return ( targetX * rsi . Size . X , targetY * rsi . Size . Y , rsi . Size . X , rsi . Size . Y ) ;
}
2022-01-07 18:54:06 +01:00
2023-08-07 18:07:08 -07:00
var dir = entity . Sprite . GetLayerDirectionCount ( layer ) switch
2022-03-01 18:23:28 -06:00
{
0 = > 0 ,
2023-08-07 18:07:08 -07:00
_ = > ( int ) layer . EffectiveDirection ( worldRotation )
2022-03-01 18:23:28 -06:00
} ;
2022-01-07 18:54:06 +01:00
2022-03-01 18:23:28 -06:00
var ( x , y , width , height ) = GetRsiFrame ( rsi , image , entity , layer , dir ) ;
2022-01-07 18:54:06 +01:00
2022-07-14 08:38:21 -07:00
var rect = new Rectangle ( x , y , width , height ) ;
2024-01-12 23:22:01 +01:00
if ( ! new Rectangle ( Point . Empty , image . Size ) . Contains ( rect ) )
2022-07-14 08:38:21 -07:00
{
2023-06-26 01:56:20 +10:00
Console . WriteLine ( $"Invalid layer {rsi!.Path}/{layer.RsiState.Name}.png for entity {_sEntityManager.ToPrettyString(entity.Owner)} at ({entity.X}, {entity.Y})" ) ;
2022-07-14 08:38:21 -07:00
return ;
}
image . Mutate ( o = > o . Crop ( rect ) ) ;
2022-01-07 18:54:06 +01:00
var colorMix = entity . Sprite . Color * layer . Color ;
var imageColor = Color . FromRgba ( colorMix . RByte , colorMix . GByte , colorMix . BByte , colorMix . AByte ) ;
var coloredImage = new Image < Rgba32 > ( image . Width , image . Height ) ;
coloredImage . Mutate ( o = > o . BackgroundColor ( imageColor ) ) ;
2022-03-01 18:23:28 -06:00
var ( imgX , imgY ) = rsi ? . Size ? ? ( EyeManager . PixelsPerMeter , EyeManager . PixelsPerMeter ) ;
2022-01-07 18:54:06 +01:00
image . Mutate ( o = > o
. DrawImage ( coloredImage , PixelColorBlendingMode . Multiply , PixelAlphaCompositionMode . SrcAtop , 1 )
2022-03-01 18:23:28 -06:00
. Resize ( imgX , imgY )
2022-01-07 18:54:06 +01:00
. Flip ( FlipMode . Vertical ) ) ;
2023-08-07 18:07:08 -07:00
var pointX = ( int ) entity . X - imgX / 2 + EyeManager . PixelsPerMeter / 2 ;
var pointY = ( int ) entity . Y - imgY / 2 + EyeManager . PixelsPerMeter / 2 ;
2022-01-07 18:54:06 +01:00
canvas . Mutate ( o = > o . DrawImage ( image , new Point ( pointX , pointY ) , 1 ) ) ;
}
}
}