using System; using System.Collections.Generic; namespace Sandbox.Clutter; public sealed partial class ClutterGridSystem { /// /// Manages storage and serialization of painted clutter instances. /// Uses binary serialization via BlobData for efficient storage. /// public sealed class ClutterStorage : BlobData { public override int Version => 1; public record struct Instance( Vector3 Position, Rotation Rotation, float Scale = 1f ); private Dictionary> _instances = []; public ClutterStorage() { } /// /// Gets the total number of instances across all models. /// public int TotalCount { get { var count = 0; foreach ( var list in _instances.Values ) count += list.Count; return count; } } /// /// Gets all model paths that have instances. /// public IEnumerable ModelPaths => _instances.Keys; /// /// Gets instances for a specific model path. /// public IReadOnlyList GetInstances( string modelPath ) { if ( _instances.TryGetValue( modelPath, out var list ) ) return list; return []; } /// /// Gets all instances grouped by model path. /// public IReadOnlyDictionary> GetAllInstances() => _instances; /// /// Adds a single instance for a model. /// public void AddInstance( string modelPath, Vector3 position, Rotation rotation, float scale = 1f ) { if ( string.IsNullOrEmpty( modelPath ) ) return; if ( !_instances.TryGetValue( modelPath, out var list ) ) { list = []; _instances[modelPath] = list; } list.Add( new Instance( position, rotation, scale ) ); } /// /// Adds multiple instances for a model. /// public void AddInstances( string modelPath, IEnumerable instances ) { if ( string.IsNullOrEmpty( modelPath ) ) return; if ( !_instances.TryGetValue( modelPath, out var list ) ) { list = []; _instances[modelPath] = list; } list.AddRange( instances ); } /// /// Erases all instances within a radius of a position. /// public int Erase( Vector3 position, float radius ) { if ( _instances.Count == 0 ) return 0; var radiusSquared = radius * radius; var totalRemoved = 0; foreach ( var list in _instances.Values ) { var removed = list.RemoveAll( i => i.Position.DistanceSquared( position ) <= radiusSquared ); totalRemoved += removed; } return totalRemoved; } /// /// Clears all instances for a specific model. /// public bool ClearModel( string modelPath ) { return _instances.Remove( modelPath ); } /// /// Clears all instances. /// public void ClearAll() { _instances.Clear(); } /// /// Serialize to binary format. /// public override void Serialize( ref Writer writer ) { // Write model count writer.Stream.Write( _instances.Count ); foreach ( var (modelPath, instances) in _instances ) { // Write model path writer.Stream.Write( modelPath ); // Write instance count writer.Stream.Write( instances.Count ); // Write each instance foreach ( var instance in instances ) { writer.Stream.Write( instance.Position ); writer.Stream.Write( instance.Rotation ); writer.Stream.Write( instance.Scale ); } } } /// /// Deserialize from binary format. /// public override void Deserialize( ref Reader reader ) { _instances.Clear(); // Read model count var modelCount = reader.Stream.Read(); for ( int m = 0; m < modelCount; m++ ) { // Read model path var modelPath = reader.Stream.Read(); // Read instance count var instanceCount = reader.Stream.Read(); var instances = new List( instanceCount ); // Read each instance for ( int i = 0; i < instanceCount; i++ ) { var position = reader.Stream.Read(); var rotation = reader.Stream.Read(); var scale = reader.Stream.Read(); instances.Add( new Instance( position, rotation, scale ) ); } if ( !string.IsNullOrEmpty( modelPath ) && instances.Count > 0 ) { _instances[modelPath] = instances; } } } } }