using Sandbox.Utility; using System.Threading; namespace Sandbox.Resources; [Title( "Random Noise" )] [Icon( "casino" )] [ClassName( "random" )] [Expose] public class RandomTextureGenerator : TextureGenerator { public enum NoiseType { [Icon( "casino" )] [Title( "Pure Random" )] Random, [Icon( "gradient" )] [Title( "Perlin Noise" )] Perlin, [Icon( "gradient" )] [Title( "Simplex Noise" )] Simplex } public NoiseType Type { get; set; } = NoiseType.Random; public int Seed { get; set; } = 0; public Vector2Int Size { get; set; } = 64; [HideIf( nameof( Type ), NoiseType.Random )] public Vector3 Offset { get; set; } = new Vector3( 0, 0, 0 ); [HideIf( nameof( Type ), NoiseType.Random )] public float Scale { get; set; } = 5.0f; [Range( 1, 8 )] [HideIf( nameof( Type ), NoiseType.Random )] public int Octaves { get; set; } = 1; public Gradient Gradient { get; set; } = new Gradient( new Gradient.ColorFrame( 0.0f, Color.Black ), new Gradient.ColorFrame( 1.0f, Color.White ) ); [Header( "Normal Map" ), Title( "Height to Normal" )] public bool ConvertHeightToNormals { get; set; } [ShowIf( nameof( ConvertHeightToNormals ), true )] public float NormalScale { get; set; } = 1; [Hide] public override bool CacheToDisk => true; protected override async ValueTask CreateTexture( Options options, CancellationToken ct ) { var w = Size.x.Clamp( 1, 1024 * 4 ); var h = Size.y.Clamp( 1, 1024 * 4 ); var scale = (Type != NoiseType.Random) ? Scale : 1f; var pixels = new Color[w * h]; long seed = Seed; INoiseField noise = null; switch ( Type ) { case NoiseType.Perlin: if ( Octaves > 1 ) { // Use fractal noise field for multiple octaves noise = Noise.PerlinField( new Noise.FractalParameters( Seed: Seed, Octaves: Octaves ) ); } else { noise = Noise.PerlinField( new Noise.Parameters( Seed ) ); } break; case NoiseType.Simplex: if ( Octaves > 1 ) { // Use fractal noise field for multiple octaves noise = Noise.SimplexField( new Noise.FractalParameters( Seed: Seed, Octaves: Octaves ) ); } else { noise = Noise.SimplexField( new Noise.Parameters( Seed ) ); } break; default: seed = (long)(IntToRandomFloat( Seed ) * int.MaxValue); break; } await Sandbox.Utility.Parallel.ForAsync( 0, w * h, ct, ( i, t ) => { var x = i % w; var y = i / w; var noiseValue = 0f; if ( noise is not null ) { noiseValue = noise.Sample( (x + Offset.x) * scale, (y + Offset.y) * scale, Offset.z * scale ); } else { noiseValue = IntToRandomFloat( seed + i ); } pixels[i] = Gradient.Evaluate( noiseValue ); return ValueTask.CompletedTask; } ); var bitmap = new Bitmap( w, h, false ); bitmap.SetPixels( pixels ); if ( ConvertHeightToNormals ) { bitmap = bitmap.HeightmapToNormalMap( NormalScale ); } ct.ThrowIfCancellationRequested(); return bitmap.ToTexture(); } public static float IntToRandomFloat( long seed ) { unchecked { // Multiply the seed by a large prime number seed = (seed ^ 0x6D2B79F5) * 0x1B873593; seed = (seed ^ (seed >> 13)) * 0x85EBCA6B; seed = seed ^ (seed >> 16); // Normalize to a float in the range [0, 1) return (seed & 0x7FFFFFFF) / (float)int.MaxValue; } } }