DiamondPanel.SparkAtlas.cs
using System;
using Sandbox;
namespace Diamonds;
public sealed partial class DiamondPanel
{
bool drawingReferenceSparks = false;
static SparkGlowAtlas sharedSparkAtlas;
SparkGlowAtlas sparkAtlas;
sealed class SparkGlowAtlas : IDisposable
{
public const int Tile = 20, Columns = 10, Rows = 5, Density = 4;
public Texture Texture;
public Fill[] Tiles;
public int Users, Builds;
Color[] palette;
public void Ensure()
{
if ( Texture?.IsValid == true && palette == Palette ) return;
var texture = Sandbox.Texture.CreateRenderTarget().WithSize( Tile * Columns * Density, Tile * Rows * Density )
.WithFormat( ImageFormat.RGBA8888 ).Create();
try
{
using ( var p = Painter.Begin( texture ) )
{
p.Clear( Color.Transparent ); p.Scale( Density );
for ( int index = 0; index < Columns * Rows; index++ )
{
using var scope = p.Scope();
p.Translate( new Vector2( (index % Columns + 0.5f) * Tile, (index / Columns + 0.5f) * Tile ) );
float h = 0.7f + index / 9 * 0.3f;
float w = h * (0.65f + index / 3 % 3 * 0.175f);
if ( index < 45 )
DrawParticleGlow( p, [new( -w, h * 0.65f ), new( w, h * 0.65f ),
new( (index % 3 - 1) * 0.35f * w, -h )], Color.White, 0.24f, 1 );
else
{
w = 0.5f + (index - 45) * 0.35f;
DrawParticleGlow( p, [new( -w, -w ), new( w, -w ), new( w, w ), new( -w, w )], Color.White, 0.24f, 1 );
}
}
}
// Interleaved vector cores use normal blending. A Painter render target
// is premultiplied and would flush the batch twice per spark; convert once.
using ( var bitmap = texture.GetBitmap() )
{
var straight = bitmap.ToTexture( false );
texture.Dispose(); texture = straight;
}
var tiles = new Fill[Columns * Rows * Palette.Length];
for ( int color = 0; color < Palette.Length; color++ )
for ( int index = 0; index < Columns * Rows; index++ )
tiles[color * Columns * Rows + index] = Fill.Image( texture, Palette[color],
width: Tile * Columns, height: Tile * Rows,
offsetX: -(index % Columns) * Tile, offsetY: -(index / Columns) * Tile );
Texture?.Dispose(); Texture = texture; Tiles = tiles; palette = Palette; Builds++;
}
catch { texture.Dispose(); throw; }
}
public void Dispose() { Texture?.Dispose(); Texture = null; Tiles = null; palette = null; }
}
bool DrawSparkGlow( Painter p, DiamondDebris.Particle chip, float opacity )
{
if ( drawingReferenceSparks ) return false;
var matrix = p.Transform;
if ( MathF.Max( new Vector2( matrix.M11, matrix.M12 ).Length, new Vector2( matrix.M21, matrix.M22 ).Length ) > SparkGlowAtlas.Density ) return false;
var vertices = chip.Vertices;
int index;
if ( vertices.Length == 3 )
{
float h = -vertices[2].y, w = vertices[1].x;
if ( h < 0.65f || h > 1.95f || w <= 0 ) return false;
index = Math.Clamp( (int)MathF.Round( (h - 0.7f) / 0.3f ), 0, 4 ) * 9
+ Math.Clamp( (int)MathF.Round( (w / h - 0.65f) / 0.175f ), 0, 2 ) * 3
+ Math.Clamp( (int)MathF.Round( vertices[2].x / w / 0.35f ) + 1, 0, 2 );
}
else if ( vertices.Length == 4 )
{
float w = vertices[1].x;
if ( w < 0.4f || w > 2 ) return false;
index = 45 + Math.Clamp( (int)MathF.Round( (w - 0.5f) / 0.35f ), 0, 4 );
}
else return false;
if ( sparkAtlas is null ) { sparkAtlas = sharedSparkAtlas ??= new(); sparkAtlas.Users++; }
sparkAtlas.Ensure();
using var scope = p.Scope();
p.Opacity *= opacity;
p.Fill = sparkAtlas.Tiles[chip.ColorIndex * 50 + index]; p.Stroke = Stroke.None;
p.Rect( new Rect( -10, -10, 20, 20 ) );
return true;
}
void ReleaseSparkAtlas()
{
if ( sparkAtlas is null ) return;
if ( --sparkAtlas.Users == 0 )
{
sparkAtlas.Dispose();
if ( ReferenceEquals( sharedSparkAtlas, sparkAtlas ) ) sharedSparkAtlas = null;
}
sparkAtlas = null;
}
}