Part of the RoofEditor mesh tool in the editor mapping addon. It builds a throwaway roof mesh from RoofEditor.BuildRoof, collects the upward facing triangles, and uses barycentric interpolation to sample the roof skin height at any 2D point. It then emits infill strips along the four perimeter walls that run from the floor plane up to just under the roof skin, splitting each strip at intersections with roof triangle edges so the walls follow the roof profile.
namespace AdvancedMappingTools;
public sealed partial class RoofEditor
{
/// <summary>Perimeter infill extends the supporting walls up to the roof skin, without a floor across the interior.</summary>
public static void BuildWallSeat( MeshKit kit, Transform frame, float width, float depth, float halfWall, float x0, float x1, float y0, float y1, RoofShape shape, float rise, float edge, float shell, Material surface, Material trim, IReadOnlyList<Vector2> custom = null, float closureThickness = 0 )
{
var source = new MeshKit
{
LiftFlushFaces = false
};
BuildRoof( source, Transform.Zero, x0, x1, y0, y1, shape, rise, edge, surface, trim, custom );
var mesh = source.Finish();
var triangles = new List<(Vector3 A, Vector3 B, Vector3 C)>();
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];
if ( Vector3.Cross( b - a, c - a ).z > .00001f )
triangles.Add( (a, b, c) );
}
}
float Height( Vector2 p )
{
var h = float.NegativeInfinity;
foreach ( var(a, b, c)in triangles )
{
var det = (b.y - c.y) * (a.x - c.x) + (c.x - b.x) * (a.y - c.y);
if ( MathF.Abs( det ) < .000001f )
continue;
var u = ((b.y - c.y) * (p.x - c.x) + (c.x - b.x) * (p.y - c.y)) / det;
var v = ((c.y - a.y) * (p.x - c.x) + (a.x - c.x) * (p.y - c.y)) / det;
if ( u >= -.0001f && v >= -.0001f && u + v <= 1.0001f )
h = MathF.Max( h, u * a.z + v * b.z + (1 - u - v) * c.z );
}
return h;
}
var margin = MathF.Max( halfWall, .125f );
var half = closureThickness > 0 ? Math.Clamp( closureThickness * .5f, .125f, 256 ) : margin;
kit.Frame = frame;
kit.Material = trim;
kit.LiftFlushFaces = false;
void Strip( Vector2 start, Vector2 end, Vector2 across )
{
var count = Math.Clamp( (int)MathF.Ceiling( start.Distance( end ) / 8 ), 1, 512 );
var splits = Enumerable.Range( 0, count + 1 ).Select( i => i / (float)count ).ToList();
float Cross( Vector2 a, Vector2 b ) => a.x * b.y - a.y * b.x;
var direction = end - start;
foreach ( var tri in triangles )
foreach ( var pair in new[]
{
(tri.A, tri.B),
(tri.B, tri.C),
(tri.C, tri.A)
}
)
foreach ( var offset in new[]
{
-half,
half
}
)
{
var origin = start + across * offset;
var a = new Vector2( pair.Item1.x, pair.Item1.y );
var b = new Vector2( pair.Item2.x, pair.Item2.y );
var edge = b - a;
var determinant = Cross( direction, edge );
if ( MathF.Abs( determinant ) < .00001f )
continue;
var t = Cross( a - origin, edge ) / determinant;
var u = Cross( a - origin, direction ) / determinant;
if ( t > 0 && t < 1 && u >= 0 && u <= 1 )
splits.Add( t );
}
var ordered = splits.Select( t => MathF.Round( t, 5 ) ).Distinct().OrderBy( t => t ).ToArray();
for ( var i = 0; i + 1 < ordered.Length; i++ )
{
if ( (ordered[i + 1] - ordered[i]) * direction.Length < .02f )
continue;
var a = Vector2.Lerp( start, end, ordered[i] );
var b = Vector2.Lerp( start, end, ordered[i + 1] );
var points = new[]
{
a - across * half,
b - across * half,
b + across * half,
a + across * half
};
var heights = points.Select( Height ).ToArray();
// Circular forms have no skin over rectangular corners: never invent a floating infill there.
if ( heights.Any( h => !float.IsFinite( h ) ) )
continue;
var bottom = points.Select( p => new Vector3( p.x, p.y, -.5f ) ).ToArray();
var top = points.Select( ( p, j ) => new Vector3( p.x, p.y, MathF.Max( .1f, heights[j] - Math.Clamp( shell * .5f, .1f, 2 ) ) ) ).ToArray();
kit.Hex( bottom[0], bottom[1], bottom[2], bottom[3], top[0], top[1], top[2], top[3] );
}
}
var inset = half - margin;
Strip( new( -margin, inset ), new( width + margin, inset ), new( 0, 1 ) );
Strip( new( -margin, depth - inset ), new( width + margin, depth - inset ), new( 0, 1 ) );
Strip( new( inset, -margin + 2 * half ), new( inset, depth + margin - 2 * half ), new( -1, 0 ) );
Strip( new( width - inset, -margin + 2 * half ), new( width - inset, depth + margin - 2 * half ), new( -1, 0 ) );
kit.Frame = frame;
}
}