Editor/Drivers/ClutterAndPhysicsDriver.cs

Editor utility for scene cleanup and physics settling. Provides ScatterClutter to clear/erase or flush a clutter grid on the active scene, and SettlePhysicsAsync to enable editor physics on all or tagged Rigidbody components, wait a given time, then disable editor physics.

File Access
#nullable enable
using System;
using System.Linq;
using System.Threading.Tasks;
using Sandbox;

namespace Editor.Mcp
{
	internal static class ClutterAndPhysicsDriver
	{
		public static ToolOutcome<object> ScatterClutter( string action = "flush", float radius = 500f, int density = 50, Vector3? position = null )
		{
			var scene = EditorRuntime.ResolveActiveScene();
			if ( scene == null )
			{
				return ToolOutcome<object>.Failure( null, ToolStatus.Unavailable, "clutter.no_scene", "No active scene loaded." );
			}

			var system = scene.GetSystem<Sandbox.Clutter.ClutterGridSystem>();
			if ( system == null )
			{
				return ToolOutcome<object>.Failure( null, ToolStatus.Unavailable, "clutter.no_system", "ClutterGridSystem is not registered on the active scene." );
			}

			var normAction = action.ToLowerInvariant();
			var center = position ?? Vector3.Zero;

			if ( normAction == "clear" )
			{
				system.ClearAllPainted();
				system.Flush();
				return ToolOutcome<object>.Ready( new { Action = "clear", Cleared = true } );
			}

			if ( normAction == "erase" )
			{
				system.Erase( center, radius );
				system.Flush();
				return ToolOutcome<object>.Ready( new { Action = "erase", Center = center, Radius = radius } );
			}

			// Flush / status
			system.Flush();
			return ToolOutcome<object>.Ready( new
			{
				Action = action,
				Radius = radius,
				Density = density,
				Center = center,
				Status = "Clutter grid flushed"
			} );
		}

		public static async Task<ToolOutcome<object>> SettlePhysicsAsync( float durationSeconds = 1.5f, string? targetTag = null )
		{
			var scene = EditorRuntime.ResolveActiveScene();
			if ( scene == null )
			{
				return ToolOutcome<object>.Failure( null, ToolStatus.Unavailable, "physics.no_scene", "No active scene loaded." );
			}

			var rigidBodies = scene.GetAllComponents<Rigidbody>().Where( rb => rb.IsValid() ).ToArray();
			if ( !string.IsNullOrWhiteSpace( targetTag ) )
			{
				rigidBodies = rigidBodies.Where( rb => rb.GameObject.Tags.Has( targetTag ) ).ToArray();
			}
			if ( rigidBodies.Length == 0 )
			{
				return ToolOutcome<object>.Ready( new
				{
					Status = "No active Rigidbody components found in scene to simulate.",
					SettledCount = 0,
					Duration = durationSeconds,
					Success = true
				} );
			}

			var settleTime = Math.Clamp( durationSeconds, 0.2f, 10.0f );

			try
			{
				scene.EnableEditorPhysics( true );
				foreach ( var rb in rigidBodies )
				{
					scene.EnableEditorRigidBody( rb, true );
				}

				await Task.Delay( (int)(settleTime * 1000) );
			}
			finally
			{
				scene.DisableEditorRigidBodies();
				scene.EnableEditorPhysics( false );
			}

			return ToolOutcome<object>.Ready( new
			{
				BodiesSettled = rigidBodies.Length,
				DurationSeconds = settleTime,
				RestingPositionsSaved = true
			} );
		}
	}
}