Editor/FoliagePainter.cs
using System;

namespace Editor.FoliagePainter;
using Foliage;
[EditorTool] // this class is an editor tool
[Title( "Foliage" )] // title of your tool
[Icon( "grass" )] // icon name from https://fonts.google.com/icons?selected=Material+Icons
public class FoliagePainter : EditorTool
{
	private float PaintProgress { get; set; } = 0f;
	public FoliageSettings FoliageSettings { get; private set; } = new();
	public override void OnEnabled()
	{
		AllowGameObjectSelection = false;
		if ( GetSelectedComponent<FoliageRenderer>() == null )
		{
			Selection.Clear();
			var first = Scene.GetAllComponents<FoliageRenderer>().FirstOrDefault();
			if ( first is not null ) Selection.Add( first.GameObject );
		}
		
		var foliageSettings = new FoliageSettingsWidgetWindow( SceneOverlay, EditorUtility.GetSerializedObject( FoliageSettings ) );
		AddOverlay( foliageSettings, TextFlag.RightBottom, 10 );
	}

	private bool Painted { get; set; } = false;


	private void Paint(SceneTraceResult tr)
	{

		var paintTarget = GetSelectedComponent<FoliageRenderer>();
		if ( paintTarget == null ) { return; }
					
		var randomPos =
			new Vector3( Random.Shared.Float( FoliageSettings.Size * -1, FoliageSettings.Size ),
				Random.Shared.Float( FoliageSettings.Size * -1, FoliageSettings.Size ), FoliageSettings.Size ).RotateAround( Vector3.Zero,
				Rotation.LookAt( tr.Normal ) * Rotation.FromPitch( 90f ) );

		randomPos = randomPos.ClampLength( FoliageSettings.Size );
					
		var paintTr = Scene.Trace.Ray( new Ray( tr.HitPosition + randomPos,Rotation.LookAt( tr.Normal ) * Rotation.FromPitch( 90f ).Down ), FoliageSettings.Size+2 )
			.UseRenderMeshes( true )
			.UsePhysicsWorld( false )
			.WithoutTags( "invisible" )
			.Run();

		if ( paintTr.Hit )
		{
			if ( 1-((paintTr.Normal.Dot( new Vector3( 0, 0, 1 ) ) + 1f) / 2f) > FoliageSettings.Foliage.MaxNormal / 180 )
			{
				return;
			}
						
						
			var randomRot =
				Rotation.FromYaw( FoliageSettings.Foliage.RandomRoll ? Random.Shared.Float( 0, 360 ) : 0 );
			if ( FoliageSettings.Foliage.RandomRotation )
			{
				randomRot = Rotation.Random;
			}
						
			var transform = new Transform( paintTr.HitPosition+(tr.Normal* (FoliageSettings.Foliage.ZOffset.GetValue())) );
			if ( FoliageSettings.Foliage.AlignToNormal )
			{
				transform = transform.WithRotation( Rotation.LookAt( paintTr.Normal )*Rotation.FromPitch( 90f )*randomRot );
			}
			else
			{
				transform = transform.WithRotation( randomRot );
			}
						
			transform = transform.WithScale(
				FoliageSettings.Foliage.Scale.GetValue());
					
			paintTarget.PaintFoliage( FoliageSettings.Foliage ,transform );
			Painted = true;
		}
	}
	public override void OnUpdate()
	{
		var tr = Scene.Trace.Ray( Gizmo.CurrentRay, 5000 )
			.UseRenderMeshes( true )
			.UsePhysicsWorld( false )
			.WithoutTags( "invisible" )
			.Run();


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

			}

			if ( Gizmo.IsLeftMouseDown && Application.KeyboardModifiers.HasFlag( Sandbox.KeyboardModifiers.Shift ) )
			{
				var paintTarget = GetSelectedComponent<FoliageRenderer>();
				if ( paintTarget == null ) { return; }

				if ( FoliageSettings.EraseOnlySelectedFoliage )
				{
					paintTarget.EraseFoliage( tr.HitPosition, FoliageSettings.Size, FoliageSettings.Foliage.ResourceId );
				}
				else
				{
					paintTarget.EraseFoliage( tr.HitPosition, FoliageSettings.Size, 0 );
				}
				
				Painted = true;
			}

			if ( !Gizmo.IsLeftMouseDown && Painted )
			{
				Painted = false;
				SceneEditorSession.Active.FullUndoSnapshot("Paint Grass");
				
				var paintTarget = GetSelectedComponent<FoliageRenderer>();
				if ( paintTarget == null ) { return; }
				paintTarget.UpdateRenderers();
			}

			if ( Gizmo.IsLeftMouseDown && !Application.KeyboardModifiers.HasFlag( Sandbox.KeyboardModifiers.Shift ))
			{
				if ( GetSelectedComponent<FoliageRenderer>() == null )
				{
					Selection.Clear();
					var first = Scene.GetAllComponents<FoliageRenderer>().FirstOrDefault();
					if ( first is not null ) Selection.Add( first.GameObject );
					return;
				}
				
				if ( FoliageSettings.Foliage == null ) { return;}
				PaintProgress += Time.Delta * FoliageSettings.PaintSpeed * 3f;
				if ( PaintProgress >= 1 )
				{
					for ( int i = 0; i < (int)PaintProgress.Floor(); i++ )
					{
						Paint( tr );
					}
					PaintProgress = 0;
				}
			}
			
		}
	}
}