Editor/Roof/ArchRoofRake.cs
using System;
using System.Collections.Generic;
using Sandbox;
namespace Sunless.Architecture;
// One edge of the deck, dragged. A fall is a thing you aim at a street, and typing a pitch into a box is the one
// way of saying it nobody can picture - so this is the inverse of ArchRoofPlane.At: told what an edge should stand
// at, it hands back the style, the pitch and the direction of fall that put it there.
public static class ArchRoofRake {
// Only an edge standing at ONE height along its whole run. A flat deck has four of them; a shed has the two it
// falls between. A gable eave is level too, but dropping it would have to argue with the ridge over it.
public static bool Carries( ArchRoofPart roof, Vector2 outward ) {
if ( !Axial( outward, out var acrossY ) ) {
return false;
}
return roof.Style == RoofStyle.Flat || (roof.Style == RoofStyle.Shed && acrossY == roof.RidgeAlongX);
}
// Every edge worth a grip, as the point it is grabbed at and the height it stands at now - read through the
// same resolve the deck is lofted from, so the widget cannot sit off the surface it is dragging.
public static IEnumerable<(int Index, Vector2 At, Vector2 Outward, float Height)> Grips( ArchRoofPart roof ) {
var outline = ArchFootprint.Wind( roof.Outline() );
for ( var index = 0; index < outline.Count; index++ ) {
var from = outline[index];
var to = outline[(index + 1) % outline.Count];
if ( (to - from).Length < 1f ) {
continue;
}
var outward = ArchRegion.Outward( from, to );
if ( !Carries( roof, outward ) ) {
continue;
}
var at = (from + to) * 0.5f;
yield return (index, at, outward, ArchRoofPlane.At( roof, at ));
}
}
// The opposite edge keeps the height it already had, so one drag moves one edge. Level again means flat again -
// the same gesture undoes itself rather than leaving a shed of no pitch behind.
public static void Apply( ArchRoofPart roof, Vector2 at, Vector2 outward, float height ) {
if ( !Axial( outward, out var acrossY ) ) {
return;
}
ArchFootprint.Bounds( ArchFootprint.Wind( roof.Outline() ), out var min, out var max );
var low = acrossY ? min.y : min.x;
var high = acrossY ? max.y : max.x;
var span = MathF.Max( 1f, high - low );
var atMax = (acrossY ? outward.y : outward.x) > 0f;
var far = ArchRoofPlane.At( roof, Across( at, acrossY, atMax ? low : high ) );
var drop = MathF.Abs( height - far );
roof.RidgeAlongX = acrossY;
roof.BaseHeight = MathF.Min( height, far );
if ( drop < 0.5f ) {
roof.Style = RoofStyle.Flat;
return;
}
roof.Style = RoofStyle.Shed;
roof.Pitch = MathF.Atan( drop / span ).RadianToDegree();
roof.Reversed = height < far ? atMax : !atMax;
}
static Vector2 Across( Vector2 at, bool acrossY, float value ) {
return acrossY ? new Vector2( at.x, value ) : new Vector2( value, at.y );
}
static bool Axial( Vector2 outward, out bool acrossY ) {
acrossY = MathF.Abs( outward.y ) > MathF.Abs( outward.x );
return (acrossY ? MathF.Abs( outward.y ) : MathF.Abs( outward.x )) > 0.99f;
}
}