Code/FractalSierpinskiGenerator.cs
namespace FractalGen;
[Icon( "change_history" )]
[Title( "Fractal - Sierpinski" )]
[ClassName( "sierpinskigenerator" )]
public class FractalSierpinskiGenerator : Sandbox.Resources.TextureGenerator
{
public int MaxSize { get; set; } = 1024;
[KeyProperty]
public Color Color { get; set; } = Color.Green;
public Color BackgroundColor { get; set; } = Color.Black;
[KeyProperty]
public int Iterations { get; set; } = 7;
public float Zoom { get; set; } = 1;
public Vector2 Offset { get; set; }
[KeyProperty]
[HideIf( nameof( GradientColors ), true )]
public bool RandomColors { get; set; } = false;
[HideIf( nameof( RandomColors ), true )]
public bool GradientColors { get; set; } = false;
[HideIf( nameof( GradientColors ), false )]
public Gradient Gradient { get; set; }
public bool ShowBorders { get; set; } = false;
[HideIf( nameof( ShowBorders ), false )]
public Color BorderColor { get; set; } = Color.White;
[HideIf( nameof( ShowBorders ), false )]
public float BorderThickness { get; set; } = 1;
public bool InvertColor { get; set; }
[Hide, JsonIgnore]
public override bool CacheToDisk => true;
protected override ValueTask<Texture> CreateTexture( Options options, CancellationToken ct )
{
var bitmap = new Bitmap( MaxSize, MaxSize );
bitmap.Clear( BackgroundColor );
float centerX = bitmap.Width / 2f + Offset.x * bitmap.Width / 4f;
float centerY = bitmap.Height / 2f + Offset.y * bitmap.Height / 4f;
float size = bitmap.Width * 0.8f * Zoom;
Vector2 top = new( centerX, centerY - size / 2 );
Vector2 bottomLeft = new( centerX - size / 2, centerY + size / 2 );
Vector2 bottomRight = new( centerX + size / 2, centerY + size / 2 );
DrawSierpinskiTriangle( bitmap, top, bottomLeft, bottomRight, Iterations );
if ( InvertColor )
bitmap.InvertColor();
return ValueTask.FromResult( bitmap.ToTexture() );
}
private void DrawSierpinskiTriangle( Bitmap bitmap, Vector2 a, Vector2 b, Vector2 c, int depth )
{
if ( depth <= 0 )
{
DrawTriangle( bitmap, a, b, c, GetColorForDepth( depth, Iterations ) );
return;
}
Vector2 ab = new( (a.x + b.x) / 2, (a.y + b.y) / 2 );
Vector2 bc = new( (b.x + c.x) / 2, (b.y + c.y) / 2 );
Vector2 ca = new( (c.x + a.x) / 2, (c.y + a.y) / 2 );
DrawSierpinskiTriangle( bitmap, a, ab, ca, depth - 1 );
DrawSierpinskiTriangle( bitmap, ab, b, bc, depth - 1 );
DrawSierpinskiTriangle( bitmap, ca, bc, c, depth - 1 );
}
private void DrawTriangle( Bitmap bitmap, Vector2 a, Vector2 b, Vector2 c, Color color )
{
DrawFilledTriangle( bitmap, a, b, c, color );
if ( ShowBorders )
{
DrawLine( bitmap, a, b, BorderColor, BorderThickness );
DrawLine( bitmap, b, c, BorderColor, BorderThickness );
DrawLine( bitmap, c, a, BorderColor, BorderThickness );
}
}
private void DrawFilledTriangle( Bitmap bitmap, Vector2 v1, Vector2 v2, Vector2 v3, Color color )
{
int minX = (int)Math.Max( 0, Math.Min( Math.Min( v1.x, v2.x ), v3.x ) );
int maxX = (int)Math.Min( bitmap.Width - 1, Math.Max( Math.Max( v1.x, v2.x ), v3.x ) );
int minY = (int)Math.Max( 0, Math.Min( Math.Min( v1.y, v2.y ), v3.y ) );
int maxY = (int)Math.Min( bitmap.Height - 1, Math.Max( Math.Max( v1.y, v2.y ), v3.y ) );
for ( int y = minY; y <= maxY; y++ )
{
for ( int x = minX; x <= maxX; x++ )
{
if ( PointInTriangle( new Vector2( x, y ), v1, v2, v3 ) )
bitmap.SetPixel( x, y, color );
}
}
}
private void DrawLine( Bitmap bitmap, Vector2 start, Vector2 end, Color color, float thickness )
{
// Bresenham algorithm for drawing a line
int x0 = (int)start.x;
int y0 = (int)start.y;
int x1 = (int)end.x;
int y1 = (int)end.y;
int dx = Math.Abs( x1 - x0 );
int dy = Math.Abs( y1 - y0 );
int sx = (x0 < x1) ? 1 : -1;
int sy = (y0 < y1) ? 1 : -1;
int err = dx - dy;
int halfThickness = (int)(thickness / 2);
while ( true )
{
for ( int ty = -halfThickness; ty <= halfThickness; ty++ )
{
for ( int tx = -halfThickness; tx <= halfThickness; tx++ )
{
int px = x0 + tx;
int py = y0 + ty;
if ( px >= 0 && px < bitmap.Width && py >= 0 && py < bitmap.Height )
bitmap.SetPixel( px, py, color );
}
}
if ( x0 == x1 && y0 == y1 ) break;
int e2 = 2 * err;
if ( e2 > -dy )
{
err -= dy;
x0 += sx;
}
if ( e2 < dx )
{
err += dx;
y0 += sy;
}
}
}
private bool PointInTriangle( Vector2 pt, Vector2 v1, Vector2 v2, Vector2 v3 )
{
float d1 = Sign( pt, v1, v2 );
float d2 = Sign( pt, v2, v3 );
float d3 = Sign( pt, v3, v1 );
bool hasNeg = (d1 < 0) || (d2 < 0) || (d3 < 0);
bool hasPos = (d1 > 0) || (d2 > 0) || (d3 > 0);
return !(hasNeg && hasPos);
}
private float Sign( Vector2 p1, Vector2 p2, Vector2 p3 )
{
return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y);
}
private Color GetColorForDepth( int depth, int maxDepth )
{
if ( GradientColors )
{
float normalizedDepth = (float)(maxDepth - depth) / maxDepth;
return Gradient.Evaluate( normalizedDepth );
}
if ( RandomColors )
return Random.Shared.Color();
return Color;
}
}