editor/AdvancedMappingTools/Geometry/HouseDetailGeometry.cs

Editor helper for the Advanced Mapping Tools package that builds small house detail geometry. Imports a PolygonMesh into a MeshKit, samples the highest surface height under a 2D point with a point in triangle test, and generates a hollow 12 sided tube swept along a path with parallel transport frames and end caps.

namespace AdvancedMappingTools;
internal static class HouseDetailGeometry
{
	internal static void Append( MeshKit kit, PolygonMesh mesh ) => kit.ImportMesh( mesh, Transform.Zero );
	internal static float Height( PolygonMesh mesh, Vector2 p )
	{
		var height = float.NegativeInfinity;
		foreach ( var face in mesh.FaceHandles )
		{
			var v = mesh.GetFaceVertices( face ).Select( mesh.GetVertexPosition ).ToArray();
			for ( var i = 1; i + 1 < v.Length; i++ )
			{
				var a = v[0];
				var b = v[i];
				var c = v[i + 1];
				var d = (b.y - c.y) * (a.x - c.x) + (c.x - b.x) * (a.y - c.y);
				if ( d < .00001f )
					continue;
				var u = ((b.y - c.y) * (p.x - c.x) + (c.x - b.x) * (p.y - c.y)) / d;
				var w = ((c.y - a.y) * (p.x - c.x) + (a.x - c.x) * (p.y - c.y)) / d;
				if ( u >= -.0001f && w >= -.0001f && u + w <= 1.0001f )
					height = MathF.Max( height, u * a.z + w * b.z + (1 - u - w) * c.z );
			}
		}

		return height;
	}

	internal static void Tube( MeshKit kit, IReadOnlyList<Vector3> path, float radius, float skin )
	{
		const int sides = 12;
		radius = MathF.Max( radius, .5f );
		var inside = MathF.Max( .1f, radius - MathF.Min( skin, radius * .5f ) );
		var outer = new List<Vector3[]>();
		var inner = new List<Vector3[]>();
		Vector3? previousU = null;
		for ( var i = 0; i < path.Count; i++ )
		{
			var d = (path[Math.Min( i + 1, path.Count - 1 )] - path[Math.Max( 0, i - 1 )]).Normal;
			var u = previousU is Vector3 last ? last - d * last.Dot( d ) : Vector3.Cross( d, Vector3.Up );
			if ( u.Length < .01f )
				u = Vector3.Forward - d * d.Dot( Vector3.Forward );
			if ( u.Length < .01f )
				u = Vector3.Left - d * d.Dot( Vector3.Left );
			u = u.Normal;
			previousU = u;
			var v = Vector3.Cross( d, u ).Normal;
			Vector3[] Ring( float r ) => Enumerable.Range( 0, sides ).Select( j => path[i] + (u * MathF.Cos( j * MathF.PI * 2 / sides ) + v * MathF.Sin( j * MathF.PI * 2 / sides )) * r ).ToArray();
			outer.Add( Ring( radius ) );
			inner.Add( Ring( inside ) );
		}

		kit.BeginPart();
		for ( var i = 0; i + 1 < path.Count; i++ )
			for ( var j = 0; j < sides; j++ )
			{
				var k = (j + 1) % sides;
				kit.Face( outer[i][j], outer[i][k], outer[i + 1][k], outer[i + 1][j] );
				kit.Face( inner[i][k], inner[i][j], inner[i + 1][j], inner[i + 1][k] );
			}

		for ( var j = 0; j < sides; j++ )
		{
			var k = (j + 1) % sides;
			kit.Face( outer[0][k], outer[0][j], inner[0][j], inner[0][k] );
			kit.Face( outer[^1][j], outer[^1][k], inner[^1][k], inner[^1][j] );
		}
	}
}