editor/AdvancedMappingTools/Architecture/RoofVentEditor.cs

Editor tool that stamps roof vents (box, mushroom, turbine, pipe stack, cowl) into a map using MeshKit primitives, with size, height, sink, sides and flashing options.

namespace AdvancedMappingTools;

public enum RoofVentStyle
{
	[Title( "Box Vent" )] Box,
	Mushroom,
	Turbine,
	[Title( "Pipe Stack" )] PipeStack,
	Cowl
}

/// <summary>
/// Vents on a roof: box vents, mushroom caps, turbines, pipe stacks with a rain cap and cowls.
/// Each click sets one. It stands upright and reaches below the click, so on a pitched roof
/// the base sits in the roof instead of floating on one corner.
/// </summary>
[ToolCategory( "Roofs" )]
[Title( "Roof Ventilation" ), Icon( "air" )]
public sealed class RoofVentEditor( PrimitiveTool tool ) : StampEditor( tool )
{
	[Property, Group( "Vent" ), WideMode]
	public RoofVentStyle Style { get; set; } = RoofVentStyle.Mushroom;

	[Property, Group( "Vent" ), Range( 2, 256, slider: false ), WideMode, Description( "Across the vent." )]
	public float Size { get; set; } = 16.0f;

	[Property, Group( "Vent" ), Range( 2, 512, slider: false ), WideMode, Description( "From the roof up to the top." )]
	public float Height { get; set; } = 18.0f;

	[Property, Group( "Vent" ), Range( 0, 128, slider: false ), WideMode, Description( "How far the vent reaches into the roof. A steeper roof needs more." )]
	public float Sink { get; set; } = 6.0f;

	[Property, Group( "Vent" ), Range( 6, 48, slider: false ), WideMode, Description( "Sides of round parts." )]
	public int Sides { get; set; } = 16;

	[Property, Group( "Vent" ), Title( "Flashing" ), WideMode, Description( "A flat plate around the foot, in the trim material." )]
	public bool Flashing { get; set; } = true;

	protected override void Stamp( MeshKit kit, Transform frame, Random random )
	{
		var r = MathF.Max( Size, 2.0f ) * 0.5f;
		var h = MathF.Max( Height, 2.0f );
		var low = -MathF.Max( Sink, 0.0f );
		var sides = Math.Clamp( Sides, 6, 48 );
		var surface = kit.Material;

		switch ( Style )
		{
			case RoofVentStyle.Box:
				kit.Box( new Vector3( -r * 0.8f, -r * 0.8f, low ), new Vector3( r * 0.8f, r * 0.8f, h * 0.6f ) );
				kit.Material = TrimMaterial;
				// The hood leans out over the opening on the low side.
				kit.Box( new Vector3( -r, -r, h * 0.6f ), new Vector3( r, r, h ) );
				break;

			case RoofVentStyle.Mushroom:
				kit.Cylinder( Vector3.Zero, r * 0.45f, r * 0.45f, low, h * 0.6f, sides );
				kit.Material = TrimMaterial;
				kit.Lathe( Vector3.Up * (h * 0.55f), [new( 0.0f, 0.0f ), new( r, 0.0f ), new( r, h * 0.15f ), new( r * 0.6f, h * 0.4f ), new( 0.0f, h * 0.45f )], sides );
				break;

			case RoofVentStyle.Turbine:
				kit.Cylinder( Vector3.Zero, r * 0.55f, r * 0.55f, low, h * 0.35f, sides );
				kit.Material = TrimMaterial;
				// The vanes as a ribbed ball: a lathe with a waist, then a cap on top.
				kit.Lathe( Vector3.Up * (h * 0.35f), [new( 0.0f, 0.0f ), new( r * 0.7f, 0.0f ), new( r, h * 0.2f ), new( r * 0.95f, h * 0.45f ), new( r * 0.5f, h * 0.62f ), new( 0.0f, h * 0.65f )], sides );
				break;

			case RoofVentStyle.PipeStack:
				kit.Cylinder( Vector3.Zero, r * 0.35f, r * 0.35f, low, h * 0.85f, sides );
				kit.Material = TrimMaterial;
				kit.Cylinder( Vector3.Zero, r * 0.75f, 0.0f, h * 0.85f, h, sides );
				break;

			case RoofVentStyle.Cowl:
				kit.Cylinder( Vector3.Zero, r * 0.5f, r * 0.5f, low, h * 0.55f, sides );
				kit.Material = TrimMaterial;
				// The head turns its mouth to one side, here forward along the yaw of the stamp.
				kit.Beam( Vector3.Up * (h * 0.75f), new Vector3( r, 0.0f, h * 0.75f ), r * 1.1f, h * 0.45f );
				kit.Cylinder( Vector3.Zero, r * 0.55f, r * 0.55f, h * 0.55f, h, sides );
				break;
		}

		if ( !Flashing ) return;

		kit.Material = TrimMaterial;
		kit.Box( new Vector3( -r * 1.25f, -r * 1.25f, low ), new Vector3( r * 1.25f, r * 1.25f, low + 0.5f ) );
		kit.Material = surface;
	}
}