Editor/Pipe/ArchPipePreviews.cs

Editor preview renderers for pipe-related architecture parts. ArchPipePreview draws a pipe path ghost, node spheres and block tags; ArchBracketPreview draws a bracket volume and, when selected, a note showing hanger/run counts resolved from the plan.

Native Interop
namespace Sunless.Architecture;

public sealed class ArchPipePreview : IArchPreview
{
	public ArchKind Kind => ArchKind.Pipe;

	public void Draw( ArchPreviewContext context )
	{
		if ( context.Payload is not ArchPipePart run || context.Tool is null )
		{
			return;
		}

		var shape = context.Tool.Resolved( run.Id, () => ArchPipeShape.Resolve( context.Tool.Plan, run ) );

		Gizmo.Draw.Color = context.Selected ? Color.Yellow : Color.Cyan.WithAlpha( 0.7f );
		ArchGhost.Path( shape.Path().Select( point => point + Vector3.Up * context.Lift ).ToList(), false );

		if ( !context.Selected )
		{
			return;
		}

		foreach ( var node in run.Nodes )
		{
			Gizmo.Draw.LineSphere( node.Position + Vector3.Up * context.Lift, MathF.Max( 3f, run.Radius ) );
		}

		foreach ( var block in shape.Blocks )
		{
			ArchGhost.Tag( shape.At( (block.From + block.To) * 0.5f ) + Vector3.Up * context.Lift,
				$"over {block.Name}", Color.Orange );
		}
	}
}

public sealed class ArchBracketPreview : IArchPreview
{
	public ArchKind Kind => ArchKind.Bracket;

	public void Draw( ArchPreviewContext context )
	{
		if ( context.Payload is not ArchPipeBracketPart bracket || context.Tool is null )
		{
			return;
		}

		Gizmo.Draw.Color = context.Selected ? Color.Yellow : Color.Green.WithAlpha( 0.35f );
		ArchGhost.Volume( bracket.Min, bracket.Max, bracket.BaseHeight + context.Lift, bracket.TopHeight + context.Lift );

		if ( !context.Selected )
		{
			return;
		}

		var shape = context.Tool.Resolved( bracket.Id, () => ArchPipeBracket.Resolve( context.Tool.Plan, bracket ) );

		ArchGhost.Note( new Vector3( bracket.Max.x, bracket.Max.y, bracket.TopHeight + context.Lift ),
			$"{shape.Count} hangers · holds {shape.Held.Count} runs" );
	}
}