editor/AdvancedMappingTools/Geometry/DrainageChannelEditor.cs

Editor tool for the Advanced Mapping Tools package that builds a drainage channel primitive along a drawn path: floor, two side walls, and optional grate bars via MeshKit sweeps.

namespace AdvancedMappingTools;

/// <summary>
/// An open drainage channel along a path: floor, two walls and optional grate bars.
/// </summary>
[ToolCategory( "Roads & Paths" )]
[Title( "Drainage Channel" ), Icon( "water" )]
public sealed class DrainageChannelEditor( PrimitiveTool tool ) : PathEditor( tool )
{
	protected override bool SnapsToLines => true;
	protected override bool CanClose => true;

	protected override int MinPoints => 2;

	[Property, Group( "Channel" ), Range( 4, 256, slider: false ), WideMode]
	public float Width { get; set; } = 24.0f;

	[Property, Group( "Channel" ), Range( 4, 256, slider: false ), WideMode]
	public float Depth { get; set; } = 20.0f;

	[Property, Group( "Channel" ), Title( "Wall" ), Range( 0.5f, 32, slider: false ), WideMode]
	public float Thickness { get; set; } = 3.0f;

	[Property, Group( "Channel" ), Title( "Grate" ), WideMode]
	public bool Grate { get; set; } = false;

	[Property, ShowIf( nameof( Grate ), true ), Group( "Channel" ), Title( "Grate Spacing" ), Range( 4, 128, slider: false ), WideMode]
	public float GrateSpacing { get; set; } = 12.0f;

	protected override void BuildMesh( MeshKit kit )
	{
		var path = SamplePath();

		if ( path.Count < 2 ) return;

		var half = MathF.Max( Width, 4.0f ) * 0.5f;
		var depth = MathF.Max( Depth, 4.0f );
		var t = Math.Clamp( Thickness, 0.5f, 32.0f );

		kit.Material = SurfaceMaterial;
		// Floor only between the walls, so floor and walls do not share faces.
		SweepPath( kit, path, Closed, [new( -half + t, -depth ), new( half - t, -depth ), new( half - t, -depth + t ), new( -half + t, -depth + t )] );
		SweepPath( kit, path, Closed, [new( -half, -depth + t ), new( -half + t, -depth + t ), new( -half + t, 0.0f ), new( -half, 0.0f )] );
		SweepPath( kit, path, Closed, [new( half - t, -depth + t ), new( half, -depth + t ), new( half, 0.0f ), new( half - t, 0.0f )] );

		if ( !Grate ) return;

		kit.Material = TrimMaterial;

		foreach ( var (position, direction) in WalkPath( path, Closed, MathF.Max( GrateSpacing, 4.0f ) ) )
		{
			var n = new Vector3( -direction.y, direction.x, 0.0f ).Normal * (half - t);

			kit.Beam( position + n + Vector3.Up * -1.0f, position - n + Vector3.Up * -1.0f, 2.0f, 2.0f );
		}
	}
}