Procedural seeding helpers for the AdvancedMappingTools editor tooling. StableSeed hashes a master seed with a part path so generation order cannot change results, SeedStream is a small xorshift random stream, and SeedSet stores per-category seeds with lock support.
namespace AdvancedMappingTools;
/// <summary>
/// Turns a seed and a stable part path into a new seed. The result depends on nothing else,
/// so the order parts are generated in can never change what a part looks like.
/// </summary>
public static class StableSeed
{
public static int Of( int seed, string path )
{
var hash = (2166136261u ^ (uint)seed) * 16777619u;
foreach ( var c in path )
{
hash ^= c;
hash *= 16777619u;
}
// Spreads the bits, paths that differ in one letter should not give close seeds.
hash ^= hash >> 15;
hash *= 0x2c1b3c6du;
hash ^= hash >> 12;
hash *= 0x297a2d39u;
hash ^= hash >> 15;
return (int)(hash & 0x7fffffff);
}
}
/// <summary>
/// A small random stream with a fixed algorithm, so results never change with the runtime.
/// </summary>
public struct SeedStream
{
uint _state;
public SeedStream( int seed )
{
_state = (uint)seed * 2654435761u + 1u;
if ( _state == 0u ) _state = 1u;
}
/// <summary>
/// The next value from 0 up to but not including 1.
/// </summary>
public float Next()
{
_state ^= _state << 13;
_state ^= _state >> 17;
_state ^= _state << 5;
return (_state >> 8) * (1.0f / 16777216.0f);
}
public float Range( float min, float max ) => min + (max - min) * Next();
public bool Chance( float probability ) => Next() < probability;
}
/// <summary>
/// One independent seed per category of a generated object. Changing the roof seed
/// cannot touch windows or walls because they never read it.
/// </summary>
public sealed class SeedSet
{
public static readonly string[] Names = ["wall", "floor", "frame", "glass", "door", "stair", "roof"];
public int Master { get; set; } = 1;
public Dictionary<string, int> Categories { get; set; } = [];
public List<string> Locked { get; set; } = [];
public int For( string category ) =>
Categories.TryGetValue( category, out var seed ) ? seed : StableSeed.Of( Master, category );
public bool IsLocked( string category ) => Locked.Contains( category );
public void SetLocked( string category, bool locked )
{
Locked.Remove( category );
if ( locked ) Locked.Add( category );
}
/// <summary>
/// Give one category a new seed. A locked category keeps its seed.
/// </summary>
public bool Randomize( string category, int? value = null )
{
if ( IsLocked( category ) ) return false;
Categories[category] = value ?? Random.Shared.Next( 1, int.MaxValue );
return true;
}
public void RandomizeAll( IEnumerable<string> names )
{
foreach ( var name in names )
Randomize( name );
}
}