editor/AdvancedMappingTools/Geometry/SidewalkEditor.cs

Editor tool that generates a sidewalk and curb mesh along a drawn path. Sweeps a cross-section profile for the slab and one or two curb strips, with side, width, height and curb dimensions as properties.

namespace AdvancedMappingTools;

public enum SidewalkSide
{
	Left,
	Right,
	Both
}

/// <summary>
/// A sidewalk with a raised curb along a path. The path is the road edge.
/// </summary>
[ToolCategory( "Roads & Paths" )]
[Title( "Sidewalk & Curb" ), Icon( "directions_walk" )]
public sealed class SidewalkEditor( PrimitiveTool tool ) : PathEditor( tool )
{
	protected override bool SnapsToLines => true;
	protected override bool CanClose => true;

	protected override int MinPoints => 2;

	[Property, Group( "Sidewalk" ), WideMode]
	public SidewalkSide Side { get; set; } = SidewalkSide.Left;

	[Property, Group( "Sidewalk" ), Range( 8, 512, slider: false ), WideMode]
	public float Width { get; set; } = 48.0f;

	[Property, Group( "Sidewalk" ), Range( 1, 64, slider: false ), WideMode]
	public float Height { get; set; } = 6.0f;

	[Property, Group( "Sidewalk" ), Title( "Curb Height" ), Range( 1, 32, slider: false ), WideMode]
	public float CurbHeight { get; set; } = 8.0f;

	[Property, Group( "Sidewalk" ), Title( "Curb Width" ), Range( 1, 32, slider: false ), WideMode]
	public float CurbWidth { get; set; } = 4.0f;

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

		if ( path.Count < 2 ) return;

		var width = MathF.Max( Width, 8.0f );
		var height = MathF.Max( Height, 1.0f );
		var curb = MathF.Max( CurbHeight, 1.0f );
		var curbWidth = MathF.Max( CurbWidth, 1.0f );

		kit.Material = SurfaceMaterial;

		if ( Side == SidewalkSide.Both )
		{
			// One slab across, so the two halves do not share a face in the middle.
			SweepPath( kit, path, Closed, [new( -width, 0.0f ), new( width, 0.0f ), new( width, height ), new( -width, height )] );
		}
		else if ( Side is SidewalkSide.Left )
		{
			SweepPath( kit, path, Closed, [new( 0.0f, 0.0f ), new( width, 0.0f ), new( width, height ), new( 0.0f, height )] );
		}
		else
		{
			SweepPath( kit, path, Closed, [new( -width, 0.0f ), new( 0.0f, 0.0f ), new( 0.0f, height ), new( -width, height )] );
		}

		kit.Material = TrimMaterial;

		if ( Side == SidewalkSide.Both )
		{
			// Curbs on the outer edges, not in the middle.
			SweepPath( kit, path, Closed, [new( -width - curbWidth, 0.0f ), new( -width, 0.0f ), new( -width, curb ), new( -width - curbWidth, curb )] );
			SweepPath( kit, path, Closed, [new( width, 0.0f ), new( width + curbWidth, 0.0f ), new( width + curbWidth, curb ), new( width, curb )] );
		}
		else if ( Side is SidewalkSide.Left )
		{
			SweepPath( kit, path, Closed, [new( -curbWidth, 0.0f ), new( 0.0f, 0.0f ), new( 0.0f, curb ), new( -curbWidth, curb )] );
		}
		else
		{
			SweepPath( kit, path, Closed, [new( 0.0f, 0.0f ), new( curbWidth, 0.0f ), new( curbWidth, curb ), new( 0.0f, curb )] );
		}
	}
}