editor/AdvancedMappingTools/Architecture/FoundationEditor.cs

Editor tool for the Advanced Mapping Tools package. Places a rectangular foundation slab with an optional wider footing, fitting the footing bottom to the ground surface when FitToGround is on, and tags the built object with amt.foundation.

namespace AdvancedMappingTools;
/// <summary>
/// A rectangular foundation pad with a projecting footing. One click per pad.
/// </summary>
[ToolCategory( "Buildings" )]
[Title( "Foundation" ), Icon( "foundation" )]
public sealed class FoundationEditor( PrimitiveTool tool ) : GroundAwareStampEditor( tool )
{
	[Property, Group( "Foundation" ), Range( 8, 4096, slider: false ), WideMode]
	public float Width { get; set; } = 256.0f;

	[Property, Group( "Foundation" ), Range( 8, 4096, slider: false ), WideMode]
	public float Depth { get; set; } = 192.0f;

	[Property, Group( "Foundation" ), Range( 1, 128, slider: false ), WideMode]
	public float Thickness { get; set; } = 8.0f;

	[Property, Group( "Foundation" ), Title( "Footing Depth" ), Range( 0, 128, slider: false ), WideMode]
	public float FootingDepth { get; set; } = 16.0f;

	[Property, Group( "Foundation" ), Title( "Footing Width" ), Range( 0, 128, slider: false ), WideMode]
	public float FootingWidth { get; set; } = 10.0f;

	protected override void OnBuilt( MeshComponent component )
	{
		base.OnBuilt( component );
		component.GameObject.Tags.Add( "amt.foundation" );
	}

	protected override void Stamp( MeshKit kit, Transform frame, Random random )
	{
		var w = Width * 0.5f;
		var d = Depth * 0.5f;
		var footing = MathF.Max( FootingDepth, 0.0f );
		if ( footing > 0 && FootingWidth > 0 )
		{
			var corners = new[]
			{
				new Vector3( -w - FootingWidth, -d - FootingWidth, 0 ),
				new Vector3( w + FootingWidth, -d - FootingWidth, 0 ),
				new Vector3( w + FootingWidth, d + FootingWidth, 0 ),
				new Vector3( -w - FootingWidth, d + FootingWidth, 0 )
			};
			if ( FitToGround )
			{
				var read = GroundRead ?? SurfaceSolver.Begin( SceneEditorSession.Active?.Scene ?? Game.ActiveScene );
				var samples = read.ProbePath( corners.Select( c => frame.PointToWorld( c ) ), Vector3.Down, Options() );
				var bottom = corners.Select( ( c, i ) => read.AcceptBelow( samples[i], frame.PointToLocal( samples[i].Position ).z, 0 ) ? c.WithZ( frame.PointToLocal( samples[i].Position ).z ) : c - Vector3.Up * footing ).ToArray();
				kit.BeginPart();
				kit.Face( bottom[2], bottom[1], bottom[0] );
				kit.Face( bottom[3], bottom[2], bottom[0] );
				kit.Face( corners );
				for ( var i = 0; i < 4; i++ )
				{
					var j = (i + 1) % 4;
					kit.Face( bottom[i], bottom[j], corners[j], corners[i] );
				}

				SurfaceDebug.Record( Title, read );
			}
			else
				kit.Box( new( -w - FootingWidth, -d - FootingWidth, -footing ), new( w + FootingWidth, d + FootingWidth, 0 ) );
		}

		kit.Box( new Vector3( -w, -d, 0.0f ), new Vector3( w, d, MathF.Max( Thickness, 1.0f ) ) );
	}
}