DiamondAtmosphere.Static.cs
using System;
using Sandbox;
namespace Diamonds;
public sealed partial class DiamondAtmosphere
{
bool disableFluid;
float[] staticDye;
DiamondBoard staticBoard;
int staticResetVersion;
int staticLayoutRevision;
Random staticRandom;
/// <summary>Replace live fluid with a sparse, dim field baked once per run.</summary>
public bool DisableFluid
{
get => disableFluid;
set
{
if ( value == disableFluid ) return;
disableFluid = value;
DyeVersion++;
previousBoard = null;
synchronizeOnEnable = true;
}
}
public float RenderOpacity => Opacity * (DisableFluid ? 0.5f : 1f);
void PrepareStaticBackground( DiamondBoard board )
{
if ( staticDye?.Length == Count * DiamondBoard.ColorCount &&
staticBoard == board && staticResetVersion == board.ResetVersion && staticLayoutRevision == 2 ) return;
staticRandom ??= new Random();
staticDye = new float[Count * DiamondBoard.ColorCount];
staticBoard = board;
staticResetVersion = board.ResetVersion;
staticLayoutRevision = 2;
float Range( float min, float max ) => min + (max - min) * (float)staticRandom.NextDouble();
// Five separated seed patches, with their colors shuffled independently of gameplay.
var centers = new Vector2[DiamondBoard.ColorCount];
var radii = new Vector2[centers.Length];
var colors = new int[] { 0, 1, 2, 3, 4 };
for ( int i = colors.Length - 1; i > 0; i-- )
{
int j = staticRandom.Next( i + 1 );
(colors[i], colors[j]) = (colors[j], colors[i]);
}
for ( int i = 0; i < centers.Length; i++ )
{
centers[i] = new Vector2( DiamondBoard.BoardWidth * Range( i % 2 == 0 ? 0.12f : 0.54f, i % 2 == 0 ? 0.46f : 0.88f ),
DiamondBoard.BoardHeight * ((i + Range( 0.2f, 0.8f )) / centers.Length) );
radii[i] = new Vector2( Range( 170, 245 ), Range( 150, 220 ) );
}
var forces = new Vector2[7];
var reach = new float[forces.Length];
var turns = new float[forces.Length];
for ( int i = 0; i < forces.Length; i++ )
{
forces[i] = new Vector2( Range( 0, DiamondBoard.BoardWidth ), Range( 0, DiamondBoard.BoardHeight ) );
reach[i] = Range( 180, 380 );
turns[i] = Range( 0.65f, 1.15f ) * (staticRandom.Next( 2 ) == 0 ? -1 : 1);
}
// Apply weaker swirls together over eight small steps. A single strong twist
// per force stretched some seeds into sub-cell ribbons that looked dotted.
// Average four sub-cell samples to filter the remaining narrow edges before
// the normal Painter texture filtering. All work still happens only at bake time.
const int steps = 8, samplesPerAxis = 2;
const float duration = 1.5f;
// The force field is much broader than a cell. Trace its corners once and
// interpolate the four dye samples, rather than repeating the costly trace.
const int traceColumns = Columns + 1;
var traced = new Vector2[traceColumns * (Rows + 1)];
for ( int y = 0; y <= Rows; y++ )
for ( int x = 0; x <= Columns; x++ )
{
var point = GridToWorld( x, y ) - new Vector2( CellSize * 0.5f );
// Keep a filtering margin outside the clipped board; the rest is invisible.
if ( point.x >= -CellSize * 2 && point.x <= DiamondBoard.BoardWidth + CellSize * 2 &&
point.y >= -CellSize * 2 && point.y <= DiamondBoard.BoardHeight + CellSize * 2 )
{
for ( int step = 0; step < steps; step++ )
for ( int i = 0; i < forces.Length; i++ )
{
var offset = point - forces[i];
float weight = MathF.Exp( -offset.LengthSquared / (reach[i] * reach[i]) );
float angle = turns[i] * weight * (duration / steps);
float sin = MathF.Sin( angle ), cos = MathF.Cos( angle );
point = forces[i] + new Vector2( offset.x * cos - offset.y * sin, offset.x * sin + offset.y * cos );
}
}
traced[y * traceColumns + x] = point;
}
for ( int y = 0; y < Rows; y++ )
for ( int x = 0; x < Columns; x++ )
{
int n = y * traceColumns + x;
for ( int sy = 0; sy < samplesPerAxis; sy++ )
for ( int sx = 0; sx < samplesPerAxis; sx++ )
{
float u = (sx + 0.5f) / samplesPerAxis, v = (sy + 0.5f) / samplesPerAxis;
var point = (traced[n] * (1 - u) + traced[n + 1] * u) * (1 - v) +
(traced[n + traceColumns] * (1 - u) + traced[n + traceColumns + 1] * u) * v;
for ( int i = 0; i < centers.Length; i++ )
{
var offset = (point - centers[i]) / radii[i];
float amount = MathF.Max( 0, 1 - offset.LengthSquared );
staticDye[(y * Columns + x) * DiamondBoard.ColorCount + colors[i]] +=
0.6f * amount * amount / (samplesPerAxis * samplesPerAxis);
}
}
}
DyeVersion++;
}
}