Editor/Spawner/BaseSpawnerEditor.cs
using Editor;
using Sandbox.Spawners;

namespace Sandbox.Spawner;

public class BaseSpawnerEditor: EditorTool<BaseSpawner>
{

	public override void OnUpdate()
	{
		var selected = GetSelectedComponent<BaseSpawner>();

		foreach ( var spawner in Scene.GetAllComponents<BaseSpawner>() )
		{
			if ( spawner == null || spawner.Range <= 0 ) continue;

			bool isSelected = spawner == selected;
			float half = spawner.Range * 0.5f;


			int segments = 20;

			using ( Gizmo.ObjectScope( spawner.GameObject, spawner.WorldTransform ) )
			{
				Gizmo.Draw.Color = isSelected 
					? Color.Green 
					: Color.Black;
				Gizmo.Draw.LineThickness = 2;
				Gizmo.Draw.IgnoreDepth = true;

				DrawProjectedEdge( new Vector2( -half, -half ), new Vector2(  half, -half ), segments, spawner );
				DrawProjectedEdge( new Vector2(  half, -half ), new Vector2(  half,  half ), segments, spawner );
				DrawProjectedEdge( new Vector2(  half,  half ), new Vector2( -half,  half ), segments, spawner );
				DrawProjectedEdge( new Vector2( -half,  half ), new Vector2( -half, -half ), segments, spawner );
			}
		}

	
	}
	
	private void DrawProjectedEdge( Vector2 from, Vector2 to, int segments, BaseSpawner spawner )
	{
		Vector3? prev = null;

		for ( int i = 0; i <= segments; i++ )
		{
			float t  = (float)i / segments;
			float lx = MathX.Lerp( from.x, to.x, t );
			float ly = MathX.Lerp( from.y, to.y, t );

			var worldXY = spawner.WorldTransform.PointToWorld( new Vector3( lx, ly, 0 ) );

			var tr = spawner.Scene.Trace
				.Ray( worldXY + Vector3.Up * 10000f, worldXY + Vector3.Down * 10000f )
				.WithoutTags( "player" )
				.Run();

			// stay in local space — ObjectScope handles the transform
			var hitWorld  = tr.Hit ? tr.HitPosition + Vector3.Up * 2f : worldXY;
			var localHit  = spawner.WorldTransform.PointToLocal( hitWorld );

			if ( prev.HasValue )
				Gizmo.Draw.Line( prev.Value, localHit );

			prev = localHit;
		}
	}
}