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] );
}
}
}