editor/AdvancedMappingTools/Framework/SurfaceIntegration.cs

Editor-side integration layer for surface and ground fitting in Advanced Mapping Tools. Defines ISurfaceAwareSettings, a SurfaceIntegration helper that builds a SurfaceBuildContext when surface or mount fitting is enabled, a GroundAwareStampEditor base that exposes ground probe properties and records debug info during mesh builds, plus partial additions to BalconyStudioSettings and BalconyStudioEditor for column ground fitting.

namespace AdvancedMappingTools;
public interface ISurfaceAwareSettings
{
	bool SurfaceEnabled { get; }

	SurfaceProbeOptions ProbeOptions();
}

public static class SurfaceIntegration
{
	public static SurfaceBuildContext Context( GeneratorSettings settings, Scene scene, Transform world, GameObject ignore = null ) => (settings is ISurfaceAwareSettings aware && aware.SurfaceEnabled || settings is IAnchorAwareSettings mounts && mounts.MountsEnabled) ? new( SurfaceSolver.Begin( scene, ignore.IsValid() ? [ignore] : null ), world ) : null;
}

public abstract class GroundAwareStampEditor( PrimitiveTool tool ) : StampEditor( tool )
{
	[Property, Group( "Surface & Ground" ), WideMode]
	public bool FitToGround { get; set; }

	[Property, Group( "Surface & Ground" ), Range( .01f, 100000, slider: false ), WideMode]
	public float GroundRange { get; set; } = 2048;

	[Property, Group( "Surface & Ground" ), Range( 0, 4096, slider: false ), WideMode]
	public float GroundMinimum { get; set; } = .01f;

	[Property, Group( "Surface & Ground" ), Range( -4096, 4096, slider: false ), WideMode]
	public float GroundOffset { get; set; }

	[Property, Group( "Surface & Ground" ), WideMode]
	public SurfaceSelection GroundSelection { get; set; } = SurfaceSelection.Nearest;

	[Property, Group( "Surface & Ground" ), WideMode]
	public SurfaceKind GroundKind { get; set; }

	[Property, Group( "Surface & Ground" ), WideMode]
	public string GroundIncludeTags { get; set; } = "";

	[Property, Group( "Surface & Ground" ), WideMode]
	public string GroundExcludeTags { get; set; } = "";

	[Property, Group( "Surface & Ground" ), WideMode]
	public bool ManualGroundLength { get; set; }

	[Property, Group( "Surface & Ground" ), Range( 1, 100000, slider: false ), WideMode]
	public float GroundLength { get; set; } = 160;

	[Property, Group( "Surface & Ground" ), WideMode]
	public bool ShowGroundDebug { get; set; }

	[System.Text.Json.Serialization.JsonIgnore, Property, ReadOnly, Group( "Surface & Ground" ), WideMode]
	public string GroundStatus => FitToGround ? SurfaceDebug.Summary( Title ) : "Ground fitting off; original dimensions.";

	public override bool ChangesOtherSettings( string property ) => property is nameof( FitToGround ) or nameof( ManualGroundLength );
	public override IReadOnlySet<string> UnusedOptions
	{
		get
		{
			var h = new HashSet<string>( base.UnusedOptions );
			if ( !FitToGround )
				h.UnionWith( [nameof( GroundRange ), nameof( GroundMinimum ), nameof( GroundOffset ), nameof( GroundSelection ), nameof( GroundKind ), nameof( GroundIncludeTags ), nameof( GroundExcludeTags ), nameof( ManualGroundLength ), nameof( GroundLength ), nameof( ShowGroundDebug )] );
			if ( !ManualGroundLength )
				h.Add( nameof( GroundLength ) );
			return h;
		}
	}

	protected SurfaceProbeOptions Options() => new()
	{
		Enabled = FitToGround,
		MaximumDistance = GroundRange,
		MinimumDistance = GroundMinimum,
		Offset = GroundOffset,
		Selection = GroundSelection,
		Kind = GroundKind,
		IncludeTags = GroundIncludeTags,
		ExcludeTags = GroundExcludeTags,
		Manual = ManualGroundLength,
		ManualDistance = GroundLength
	};
	protected SurfaceReadSession GroundRead { get; private set; }

	protected override void BuildMesh( MeshKit kit )
	{
		GroundRead = FitToGround ? SurfaceSolver.Begin( SceneEditorSession.Active?.Scene ?? Game.ActiveScene ) : null;
		try
		{
			base.BuildMesh( kit );
		}
		finally
		{
			if ( GroundRead != null )
				SurfaceDebug.Record( Title, GroundRead );
			GroundRead = null;
		}
	}

	public override void OnUpdate( SceneTrace trace )
	{
		base.OnUpdate( trace );
		if ( FitToGround && ShowGroundDebug )
			SurfaceDebug.Draw( Title );
	}
}

public sealed partial class BalconyStudioSettings
{
	[Property, Group( "Surface & Ground" ), WideMode]
	public bool FitColumnsToGround { get; set; }

	[Property, Group( "Surface & Ground" ), Range( .01f, 100000, slider: false ), WideMode]
	public float GroundRange { get; set; } = 2048;

	[Property, Group( "Surface & Ground" ), Range( 0, 4096, slider: false ), WideMode]
	public float GroundMinimum { get; set; } = .01f;

	[Property, Group( "Surface & Ground" ), Range( -4096, 4096, slider: false ), WideMode]
	public float GroundOffset { get; set; }

	[Property, Group( "Surface & Ground" ), WideMode]
	public SurfaceSelection GroundSelection { get; set; }

	[Property, Group( "Surface & Ground" ), WideMode]
	public SurfaceKind GroundKind { get; set; }

	[Property, Group( "Surface & Ground" ), WideMode]
	public string GroundIncludeTags { get; set; } = "";

	[Property, Group( "Surface & Ground" ), WideMode]
	public string GroundExcludeTags { get; set; } = "";

	[Property, Group( "Surface & Ground" ), WideMode]
	public bool ManualGroundLength { get; set; }

	[Property, Group( "Surface & Ground" ), Range( 1, 100000, slider: false ), WideMode]
	public float GroundLength { get; set; } = 128;

	[Property, Group( "Surface & Ground" ), WideMode]
	public bool ShowGroundDebug { get; set; }

	[System.Text.Json.Serialization.JsonIgnore, Property, ReadOnly, Group( "Surface & Ground" ), WideMode]
	public string GroundStatus => SurfaceEnabled ? SurfaceDebug.Summary( "Balcony Studio" ) : "Ground fitting off; original support dimensions.";

	[System.Text.Json.Serialization.JsonIgnore]
	public bool SurfaceEnabled => FitColumnsToGround && SupportColumns;

	public SurfaceProbeOptions ProbeOptions() => new()
	{
		Enabled = SurfaceEnabled,
		MaximumDistance = GroundRange,
		MinimumDistance = GroundMinimum,
		Offset = GroundOffset,
		Selection = GroundSelection,
		Kind = GroundKind,
		IncludeTags = GroundIncludeTags,
		ExcludeTags = GroundExcludeTags,
		Manual = ManualGroundLength,
		ManualDistance = GroundLength
	};
	void HideGroundSettings( HashSet<string> h )
	{
		if ( !SupportColumns )
			h.Add( nameof( FitColumnsToGround ) );
		if ( !SurfaceEnabled )
			h.UnionWith( [nameof( GroundRange ), nameof( GroundMinimum ), nameof( GroundOffset ), nameof( GroundSelection ), nameof( GroundKind ), nameof( GroundIncludeTags ), nameof( GroundExcludeTags ), nameof( ManualGroundLength ), nameof( GroundLength ), nameof( ShowGroundDebug )] );
		if ( !ManualGroundLength )
			h.Add( nameof( GroundLength ) );
	}
}

public sealed partial class BalconyStudioEditor
{
	public override void OnUpdate( SceneTrace trace )
	{
		base.OnUpdate( trace );
		if ( Settings.SurfaceEnabled && Settings.ShowGroundDebug )
			SurfaceDebug.Draw( "Balcony Studio" );
		if ( Settings.MountsEnabled && Settings.ShowMountDebug )
			SurfaceDebug.Draw( Settings.MountOwner );
	}
}