Editor-side data model for roof, roof-light, ladder and downpipe parts used by an architecture editor. Defines enums and plain POCO classes that store geometry, style flags, palettes and helper computed properties, plus a Duplicate/Reshape API for roof parts.
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace Sunless.Architecture;
public enum RoofStyle
{
Flat,
Shed,
Gable,
Hip,
Sawtooth
}
// A monitor is the same carve standing tall enough to be a housing: clad sides, a coping, a lid on top. Roof
// plant is the same carve called differently again - so append only, because a form inserted mid-list renumbers
// every authored light after it. What each one actually builds is ArchRoofLightKinds, never a test out here.
public enum RoofLightForm
{
Kerbed,
Monitor,
Chimney,
Vent,
Tank,
Hatch,
Dormer
}
public sealed class ArchRoofPart : IArchCollides, IArchPainted, IArchNamed
{
// Overrides the kit for THIS part alone: one wall that needs its exact holes, one tower that needs none.
public ArchCollisionMode? Collision { get; set; }
public int Id { get; set; }
public string Name { get; set; } = "Roof";
// Which storey it roofs - a height alone can't say.
public int Level { get; set; }
public RoofStyle Style { get; set; } = RoofStyle.Gable;
public Vector2 Min { get; set; }
public Vector2 Max { get; set; }
public float BaseHeight { get; set; } = 128f;
public float Pitch { get; set; } = 30f;
public float Overhang { get; set; } = 16f;
public float Thickness { get; set; } = 6f;
public int SawtoothBays { get; set; } = 4;
public bool RidgeAlongX { get; set; } = true;
// Sends the fall the other way - a porch roof on the house's low side.
public bool Reversed { get; set; }
public bool Gutters { get; set; } = true;
public bool Fascia { get; set; } = true;
public bool Soffit { get; set; } = true;
public bool EndWalls { get; set; } = true;
public bool RidgeCap { get; set; } = true;
// Rafters and purlins left showing under the deck.
public bool Frame { get; set; }
public float FrameSpacing { get; set; }
public bool Ceiling { get; set; }
public bool LoftFloor { get; set; }
public bool VaultedCeiling { get; set; }
public float VaultRise { get; set; } = 48f;
// A walkway deck: the cornice runs only along the run, never across the mouths.
public bool Walkway { get; set; }
public bool Cornice { get; set; } = true;
public bool Parapet { get; set; }
public float ParapetHeight { get; set; } = 18f;
// Edge protection, standing on the parapet where there is one and on the deck where there is not.
public bool Guardrail { get; set; }
public float GuardHeight { get; set; } = 42f;
// Placed, not derived; left off Duplicate - a light is flat-deck only.
public List<ArchRoofLightPart> Lights { get; set; } = new();
// Walls standing ON the deck: a parapet that varies edge by edge, a party wall between two rooftops. Left off
// Duplicate for the reason Lights is - a split hands the new section a fresh deck, and the walls that were
// authored stay on the section they were drawn against.
public List<ArchWall> Walls { get; set; } = new();
public List<Vector2> Footprint { get; set; } = new();
public ArchPalette Palette { get; set; } = new();
public Vector2 Size => Max - Min;
public bool HasFootprint => Footprint.Count >= 4;
public List<Vector2> Outline() => ArchFootprint.OrRect( Footprint, Min, Max );
public void Reshape( List<Vector2> outline )
{
(Footprint, Min, Max) = ArchFootprint.Authored( outline );
}
public ArchRoofPart Duplicate( int id, List<Vector2> outline )
{
var (loop, min, max) = ArchFootprint.Authored( outline );
return new ArchRoofPart
{
Id = id,
Name = Name,
Level = Level,
Style = Style,
Min = min,
Max = max,
BaseHeight = BaseHeight,
Pitch = Pitch,
Overhang = Overhang,
Thickness = Thickness,
SawtoothBays = SawtoothBays,
RidgeAlongX = ArchBuild.Ridged( RidgeRun.Auto, min, max ),
Reversed = Reversed,
Gutters = Gutters,
Fascia = Fascia,
Soffit = Soffit,
EndWalls = EndWalls,
RidgeCap = RidgeCap,
Frame = Frame,
FrameSpacing = FrameSpacing,
Ceiling = Ceiling,
LoftFloor = LoftFloor,
VaultedCeiling = VaultedCeiling,
VaultRise = VaultRise,
Walkway = Walkway,
Cornice = Cornice,
Parapet = Parapet,
ParapetHeight = ParapetHeight,
Guardrail = Guardrail,
GuardHeight = GuardHeight,
Footprint = loop,
Palette = Palette
};
}
}
// The hole is a carve - the kerb lining is the reveal faces, not a second box.
public sealed class ArchRoofLightPart : IArchNamed
{
public int Id { get; set; }
public string Name { get; set; } = "RoofLight";
public RoofLightForm Form { get; set; } = RoofLightForm.Kerbed;
public Vector2 Min { get; set; }
public Vector2 Max { get; set; }
// Flush panes pond water and read as a painted rectangle.
public float Curb { get; set; } = 8f;
public float CurbWidth { get; set; } = 6f;
public bool Glazed { get; set; } = true;
// 0 leaves one sheet; else a bar wherever a pane would otherwise run wider than this.
public float PaneSpan { get; set; }
public ArchPalette Palette { get; set; } = new();
public bool IsMonitor => Form == RoofLightForm.Monitor;
public Vector2 Size => Max - Min;
public List<Vector2> Outline() => ArchFootprint.Rect( Min, Max );
}
public sealed class ArchLadderPart : IArchNamed
{
public int Id { get; set; }
public string Name { get; set; } = "Ladder";
// Anchors, not a wall id - it outlives the wall re-drawn under it.
public Vector2 Anchor { get; set; }
public Vector2 Outward { get; set; } = new( 1f, 0f );
public float BaseHeight { get; set; }
public float TopHeight { get; set; } = 128f;
public float Width { get; set; } = 18f;
public float Standoff { get; set; } = 7f;
public float RungSpacing { get; set; } = 12f;
public bool Cage { get; set; }
public float CageFrom { get; set; } = 84f;
public bool Return { get; set; } = true;
public ArchPalette Palette { get; set; } = new();
public float Rise => MathF.Max( 1f, TopHeight - BaseHeight );
public Vector2 Facing => Outward.Length < 0.01f ? new Vector2( 1f, 0f ) : Outward.Normal;
public Vector2 Along
{
get
{
var facing = Facing;
return new Vector2( -facing.y, facing.x );
}
}
}
// A conduit is a downpipe with a different profile and a bracket rhythm - no eave to tap, no hopper, no swan neck.
public enum PipeRun
{
Rainwater,
Conduit
}
public sealed class ArchDownpipePart : IArchPainted, IArchNamed
{
public int Id { get; set; }
public string Name { get; set; } = "Downpipe";
public Vector2 Outlet { get; set; }
public Vector2 Wall { get; set; }
public float TopHeight { get; set; }
public float BaseHeight { get; set; }
public string Profile { get; set; } = "downpipe";
public bool Shoe { get; set; } = true;
public PipeRun Run { get; set; } = PipeRun.Rainwater;
public ArchPalette Palette { get; set; } = new();
public bool IsConduit => Run == PipeRun.Conduit;
}