editor/AdvancedMappingTools/Geometry/QuayEditor.cs

An editor tool for Advanced Mapping Tools that builds a quay / sea wall mesh along a drawn path, with a thick wall, a projecting coping band and evenly spaced bollards on top. It is a PathEditor subclass with Height, Thickness, Coping and Bollard Spacing properties.

namespace AdvancedMappingTools;

/// <summary>
/// A quay or sea wall along a path: a thick wall, a projecting coping and bollards on top.
/// The wall stands up from the path, so the path is the water line.
/// </summary>
[ToolCategory( "Tunnels & Bridges" )]
[Title( "Quay" ), Icon( "water" )]
public sealed class QuayEditor( PrimitiveTool tool ) : PathEditor( tool )
{
	protected override bool SnapsToLines => true;

	protected override int MinPoints => 2;
	protected override bool CanClose => false;

	[Property, Group( "Quay" ), Range( 8, 1024, slider: false ), WideMode]
	public float Height { get; set; } = 96.0f;

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

	[Property, Group( "Quay" ), Title( "Coping" ), WideMode]
	public bool Coping { get; set; } = true;

	[Property, ShowIf( nameof( Coping ), true ), Group( "Quay" ), Title( "Coping Overhang" ), Range( 0, 64, slider: false ), WideMode]
	public float CopingOverhang { get; set; } = 6.0f;

	[Property, Group( "Quay" ), Title( "Bollard Spacing" ), Range( 0, 512, slider: false ), WideMode, Description( "Zero turns the bollards off." )]
	public float BollardSpacing { get; set; } = 96.0f;

	protected override bool IsShapeValid => SamplePath().Count >= 2;

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

		if ( path.Count < 2 ) return;

		var half = Thickness * 0.5f;
		var over = MathF.Max( CopingOverhang, 0.0f );
		var height = MathF.Max( Height, 4.0f );

		kit.Material = SurfaceMaterial;
		SweepPath( kit, path, Closed, [new( -half, 0.0f ), new( half, 0.0f ), new( half, height ), new( -half, height )] );

		kit.Material = TrimMaterial;

		if ( Coping )
			SweepPath( kit, path, Closed, [new( -half - over, height - 4.0f ), new( half + over, height - 4.0f ), new( half + over, height ), new( -half - over, height )] );

		if ( BollardSpacing <= 0.0f ) return;

		foreach ( var (position, _) in WalkPath( path, Closed, MathF.Max( BollardSpacing, 8.0f ) ) )
		{
			kit.Box( position + new Vector3( -4.0f, -4.0f, height ), position + new Vector3( 4.0f, 4.0f, height + 20.0f ) );
			kit.Box( position + new Vector3( -5.0f, -5.0f, height + 20.0f ), position + new Vector3( 5.0f, 5.0f, height + 23.0f ) );
		}
	}
}