Editor/AssetGenerator.cs
using System;
using Sandbox;
using Editor;
public class AssetSprayerSettings
{
	public Model AssetModel { get; set; }
	public float Width = 1024;
	public float Height = 1024;
	public float Spacing = 32;
}

[EditorTool]
[Title("Asset Sprayer")]
[Icon("shower")]
public class AssetSprayerTool : EditorTool
{
	AssetSprayerSettings Settings { get; set; } = new AssetSprayerSettings();
	
	private int K = 30;

	public AssetSprayerTool()
	{
		
		// create a widget window. This is a window that  
		// can be dragged around in the scene view
		var window = new WidgetWindow( SceneOverlay );
		window.WindowTitle = "Asset Generator";
		window.Layout = Layout.Column();
		window.Layout.Margin = 16;
		window.FixedWidth = 480f;

		var controlSheet = new ControlSheet();
		controlSheet.AddObject( Settings.GetSerialized() );
		controlSheet.Margin = 8f;

		window.Layout.Add( controlSheet );
		
		// Create a button for us to press
		var button = new Button( "Generate Random" );
		button.MaximumWidth = 128f;
		button.Pressed = GenerateSamples;
		
		var row = window.Layout.AddRow( 1 );
		row.AddStretchCell();
		row.AddStretchCell();
		row.AddStretchCell();
		row.AddStretchCell();
		row.AddStretchCell();
		row.AddStretchCell();
		row.AddStretchCell();
		row.AddStretchCell();
		row.AddStretchCell();
		row.AddStretchCell();
		row.AddStretchCell();
		row.AddStretchCell();
		row.Add( button );
		row.AddStretchCell();
		// Add the button to the window's layout
		//window.Layout.Add( button );


		AddOverlay( window, TextFlag.RightTop, 10 );
	}

	public override void OnUpdate()
	{
		var tr = Scene.Trace.Ray( Gizmo.CurrentRay, 5000 )
						.UseRenderMeshes( true )
						.UsePhysicsWorld( false )
						.WithoutTags( "sprinkled" )
						.Run();

		if ( tr.Hit )
		{
			using ( Gizmo.Scope( "cursor" ) )
			{
				Gizmo.Transform = new Transform( tr.HitPosition, Rotation.LookAt( tr.Normal ) );
				Gizmo.Draw.LineCircle( 0, 86 );
			}
		}
	}

	private void GenerateSamples()
	{
		var size = Settings.AssetModel.Bounds.Size;
		
		Vector2 rect = new Vector2( size.x, size.y );

		var radius = (float) Math.Sqrt( (rect.x * rect.x) + (rect.y * rect.y) ) / 2;

		var poisson = new PoissonDiscSamples( Settings.Width, Settings.Height, radius+Settings.Spacing, K );

		var points = poisson.GetPoints();

		var root = SceneEditorSession.Active.Scene.CreateObject();
		root.Name = "Generated Assets";
		
		foreach ( var point in points )
		{
			var gameObject = SceneEditorSession.Active.Scene.CreateObject();
			gameObject.Parent = root;
			gameObject.WorldPosition = new Vector3( point.x - (Settings.Width/2), point.y-(Settings.Height/2), 0 );

			var model = gameObject.Components.GetOrCreate<ModelRenderer>().Model = Settings.AssetModel;
		}
	}
}