A roof gutter generator for the editor's primitive tool system. Sweeps a channel profile and two side walls along a drawn path, then places downpipe cylinders at a set spacing.
namespace AdvancedMappingTools;
/// <summary>
/// A rain gutter along a roof edge: channel, side walls and downpipes at a set spacing.
/// </summary>
[ToolCategory( "Roofs" )]
[Title( "Gutter System" ), Icon( "water_drop" )]
public sealed class GutterEditor( PrimitiveTool tool ) : PathEditor( tool )
{
protected override bool SnapsToLines => true;
protected override bool CanClose => true;
protected override int MinPoints => 2;
[Property, Group( "Gutter" ), Range( 2, 64, slider: false ), WideMode]
public float Width { get; set; } = 12.0f;
[Property, Group( "Gutter" ), Range( 2, 64, slider: false ), WideMode]
public float Depth { get; set; } = 10.0f;
[Property, Group( "Gutter" ), Title( "Wall" ), Range( 0.5f, 12, slider: false ), WideMode]
public float Thickness { get; set; } = 1.5f;
[Property, Group( "Gutter" ), Title( "Downpipe Spacing" ), Range( 0, 1024, slider: false ), WideMode, Description( "Zero leaves the downpipes off." )]
public float DownpipeSpacing { get; set; } = 192.0f;
[Property, HideIf( nameof( DownpipeSpacing ), 0.0f ), Group( "Gutter" ), Title( "Downpipe Radius" ), Range( 0.5f, 8, slider: false ), WideMode]
public float DownpipeRadius { get; set; } = 2.0f;
[Property, HideIf( nameof( DownpipeSpacing ), 0.0f ), Group( "Gutter" ), Title( "Downpipe Drop" ), Range( 0, 512, slider: false ), WideMode]
public float DownpipeDrop { get; set; } = 64.0f;
protected override void BuildMesh( MeshKit kit )
{
var path = SamplePath();
if ( path.Count < 2 ) return;
var half = MathF.Max( Width, 2.0f ) * 0.5f;
var up = MathF.Max( Depth, 2.0f );
var t = Math.Clamp( Thickness, 0.5f, 12.0f );
kit.Material = TrimMaterial;
// Floor of the channel.
SweepPath( kit, path, Closed, [new( -half, 0.0f ), new( half, 0.0f ), new( half, t ), new( -half, t )] );
// Two side walls.
SweepPath( kit, path, Closed, [new( -half, t ), new( -half + t, t ), new( -half + t, up ), new( -half, up )] );
SweepPath( kit, path, Closed, [new( half - t, t ), new( half, t ), new( half, up ), new( half - t, up )] );
if ( DownpipeSpacing <= 0.0f ) return;
var radius = Math.Clamp( DownpipeRadius, 0.5f, 8.0f );
var drop = MathF.Max( DownpipeDrop, 0.0f );
foreach ( var (position, _) in WalkPath( path, Closed, MathF.Max( DownpipeSpacing, 16.0f ) ) )
kit.Cylinder( position, radius, radius, -drop, 0.0f, 8 );
}
}