Editor/Cabinet/ArchCabinetShape.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace Sunless.Architecture;
// Space is in mount frame: x out of face, y along run, z up.
public readonly record struct ArchCabinetMount( string Name, Transform Local, Vector3 Space );
public sealed class ArchCabinetCell {
public int Index { get; init; }
public Vector2 Start { get; init; }
public Vector2 End { get; init; }
public float Foot { get; init; }
public float Head { get; init; }
public float Depth { get; init; }
public CabinetSlot Slot { get; init; }
public List<ArchCabinetMount> Mounts { get; } = new();
public float Width => (End - Start).Length;
public ArchCabinetMount? Mount( string name ) {
foreach ( var mount in Mounts ) {
if ( ArchCabinetMounts.Same( mount.Name, name ) ) {
return mount;
}
}
return null;
}
}
readonly record struct ArchCabinetSquare(
Vector2 Corner, Vector2 Along, Vector2 Across, float LegAlong, float LegAcross, float DepthAlong,
float TopAlong, float BackAlong );
sealed class ArchCabinetEnd {
public float Carcass { get; private set; }
public float Counter { get; private set; }
public ArchCabinetSquare? Square { get; private set; }
public bool Standing => Carcass > 0.01f;
public void Deeper( float carcass, float counter, ArchCabinetSquare? square ) {
if ( carcass <= Carcass ) {
return;
}
Carcass = carcass;
Counter = counter;
Square = square;
}
}
public sealed class ArchCabinetJoint {
public int Index { get; init; }
public List<Vector2> Loop { get; init; } = new();
public List<Vector2> Face { get; init; } = new();
public List<Vector2> Kick { get; init; } = new();
public List<Vector2> Top { get; init; } = new();
public List<Vector2> Crown { get; init; } = new();
public float Foot { get; init; }
public float Head { get; init; }
public float Seat { get; init; }
public float Plinth { get; init; }
public float Counter { get; init; }
public float Cornice { get; init; }
public List<ArchCabinetMount> Mounts { get; } = new();
public ArchCabinetMount? Mount( string name ) {
foreach ( var mount in Mounts ) {
if ( ArchCabinetMounts.Same( mount.Name, name ) ) {
return mount;
}
}
return null;
}
}
public sealed class ArchCabinetShape {
public const float Splashback = 18f;
public const float LeastHead = 12f;
public const float LeastRun = 6f;
public const float HungDatum = 53.4375f;
public ArchCabinetType Type { get; init; }
public CabinetTier Tier { get; init; }
public Vector2 Start { get; init; }
public Vector2 End { get; init; }
public Vector2 Along { get; init; }
public Vector2 Facing { get; init; }
public float Seat { get; init; }
public float Head { get; init; }
public float Depth { get; init; }
public float TopDepth { get; init; }
public int Shelves { get; init; }
public int DrawerRows { get; init; }
public float DrawerHeight { get; init; }
public bool Handles { get; init; }
public float Thickness { get; init; }
public float EndOversail { get; init; }
public float BackOversail { get; init; }
public bool StartsOpen { get; init; }
public bool EndsOpen { get; init; }
public float CrownFrom { get; init; }
public float CrownTo { get; init; }
public float TopFrom { get; init; }
public float TopTo { get; init; }
public List<ArchCabinetCell> Cells { get; } = new();
public List<ArchCabinetJoint> Joints { get; } = new();
public bool Standing => Type is not null && (Cells.Count > 0 || Joints.Count > 0);
public float Run => (End - Start).Length;
public float Plinth => Tier == CabinetTier.Hanging ? 0f : MathF.Max( 0f, Type?.PlinthHeight ?? 0f );
public float Counter => Tier == CabinetTier.Hanging ? 0f : MathF.Max( 0f, Thickness );
public float Cornice { get; init; }
public float CorniceDepth => Cornice <= 0.01f ? Depth : Depth + MathF.Max( 0f, Type?.CorniceProjection ?? 0f );
public float CarcassFoot => Seat + Plinth;
public float CarcassHead => MathF.Max( CarcassFoot + LeastHead, Head - Counter - Cornice );
public List<Vector2> Outline() {
var near = Facing * 0f;
var far = Facing * MathF.Max( MathF.Max( Depth, TopDepth ), CorniceDepth );
return new List<Vector2> { Start + near, End + near, End + far, Start + far };
}
public static ArchCabinetShape Resolve( ArchPlan plan, ArchKit kit, ArchRoom room, ArchBuilding building, ArchCabinetPart part ) {
if ( part is null ) {
return new ArchCabinetShape();
}
var type = ArchCabinetTypes.Find( kit, part.Type );
if ( type is null ) {
return new ArchCabinetShape();
}
var tier = part.Tier == CabinetTier.Auto ? type.Tier : part.Tier;
var depth = Deep( part, type );
var top = Topped( part, type, depth );
var floor = room?.BaseHeight ?? 0f;
var seat = Seated( plan, kit, room, part, type, tier, floor );
var head = Reached( plan, kit, part, type, tier, seat, depth );
var module = Module( part, type );
Corners( plan, kit, part, type, depth, seat, head, module, out var first, out var last );
var along = part.Along;
var start = part.Start + along * first.Carcass;
var run = part.Run - first.Carcass - last.Carcass;
var stands = run >= LeastRun;
var oversail = MathF.Max( 0f, part.TopEndOversail ?? type.TopEndOversail );
var thickness = MathF.Max( 0f, part.TopThickness ?? type.TopThickness );
var counter = tier == CabinetTier.Hanging ? 0f : thickness;
var crown = counter > 0.01f ? 0f : MathF.Max( 0f, type.CorniceHeight );
var crowning = crown <= 0.01f ? 0f : MathF.Max( 0f, type.CorniceOversail );
var shape = new ArchCabinetShape {
Type = type,
Tier = tier,
Start = start,
End = start + along * (stands ? run : 0f),
Along = along,
Facing = part.Facing,
Seat = seat,
Head = head,
Depth = depth,
TopDepth = tier == CabinetTier.Hanging ? depth : top,
Shelves = Math.Max( 0, part.Shelves ?? type.Shelves ),
DrawerRows = Math.Max( 0, part.DrawerRows ?? type.DrawerRows ),
DrawerHeight = MathF.Max( 2f, type.DrawerHeight ),
Handles = part.Handles ?? type.Handles,
Thickness = thickness,
Cornice = crown,
EndOversail = oversail,
BackOversail = MathF.Max( 0f, part.TopBackOversail ?? type.TopBackOversail ),
StartsOpen = !first.Standing,
EndsOpen = !last.Standing,
CrownFrom = first.Standing ? 0f : -crowning,
CrownTo = last.Standing ? run : run + crowning,
TopFrom = first.Standing ? first.Counter - first.Carcass : -oversail,
TopTo = last.Standing ? run - (last.Counter - last.Carcass) : run + oversail
};
if ( stands ) {
Fill( shape, type, module );
}
Join( shape, type, first.Square );
Join( shape, type, last.Square );
return shape;
}
static void Join( ArchCabinetShape shape, ArchCabinetType type, ArchCabinetSquare? held ) {
if ( held is not { } square ) {
return;
}
var recess = MathF.Max( 0f, type.PlinthRecess );
var front = MathF.Max( 0.25f, type.FrontThickness );
var crown = shape.Cornice <= 0.01f ? 0f : MathF.Max( 0f, type.CorniceProjection );
var joint = new ArchCabinetJoint {
Index = shape.Joints.Count,
Loop = Pentagon( square, 0f, 0f, MathF.Max( 1f, square.DepthAlong - front ),
MathF.Max( 1f, shape.Depth - front ) ),
Face = Pentagon( square, 0f, 0f, square.DepthAlong, shape.Depth ),
Kick = Pentagon( square, 0f, 0f, MathF.Max( 1f, square.DepthAlong - recess ),
MathF.Max( 1f, shape.Depth - recess ) ),
Top = Pentagon( square, -square.BackAlong, -shape.BackOversail, square.TopAlong, shape.TopDepth ),
Crown = Pentagon( square, 0f, 0f, square.DepthAlong + crown, shape.Depth + crown ),
Foot = shape.CarcassFoot,
Head = shape.CarcassHead,
Seat = shape.Seat,
Plinth = shape.Plinth,
Counter = shape.Counter,
Cornice = shape.Cornice
};
Angled( shape, joint, square, type );
shape.Joints.Add( joint );
}
static List<Vector2> Pentagon( ArchCabinetSquare square, float backAlong, float backAcross, float frontAlong,
float frontAcross ) {
var origin = square.Corner;
var along = square.Along;
var across = square.Across;
return new List<Vector2>
{
origin + along * backAlong + across * backAcross,
origin + along * square.LegAlong + across * backAcross,
origin + along * square.LegAlong + across * frontAcross,
origin + along * frontAlong + across * square.LegAcross,
origin + along * backAlong + across * square.LegAcross
};
}
static void Angled( ArchCabinetShape shape, ArchCabinetJoint joint, ArchCabinetSquare square, ArchCabinetType type ) {
var facing = (square.Along + square.Across).Normal;
var left = new Vector2( -facing.y, facing.x );
var one = joint.Face[2];
var other = joint.Face[3];
var from = Vector2.Dot( other - one, left ) > 0f ? one : other;
var span = (other - one).Length;
var thickness = MathF.Max( 0.25f, type.FrontThickness );
var width = span - type.Reveal * 2f;
if ( type.Prefabbed ) {
joint.Mounts.Add( Angle( from, Rotation.LookAt( new Vector3( facing.x, facing.y, 0f ), Vector3.Up ),
ArchCabinetMounts.CornerCarcass, shape.Head, shape.Depth, span, shape.Head - shape.Seat ) );
return;
}
var drawer = shape.DrawerRows > 0 ? shape.DrawerHeight * shape.DrawerRows : 0f;
var opening = joint.Head - joint.Foot - drawer;
if ( width < 1f || opening < 1f ) {
return;
}
var seat = from - facing * thickness + left * type.Reveal;
var rotation = Rotation.LookAt( new Vector3( facing.x, facing.y, 0f ), Vector3.Up );
if ( drawer > 0.5f ) {
joint.Mounts.Add( Angle( seat, rotation, ArchCabinetMounts.CornerDrawer, joint.Head, thickness, width,
drawer - type.Reveal * 2f ) );
}
if ( type.Splits && type.FrontSplit < opening ) {
var split = joint.Foot + type.FrontSplit;
joint.Mounts.Add( Angle( seat, rotation, ArchCabinetMounts.CornerFront, split, thickness, width,
split - joint.Foot - type.Reveal * 2f ) );
joint.Mounts.Add( Angle( seat, rotation, ArchCabinetMounts.CornerFrontUpper, joint.Head - drawer, thickness,
width, joint.Head - drawer - split - type.Reveal * 2f ) );
} else {
joint.Mounts.Add( Angle( seat, rotation, ArchCabinetMounts.CornerFront, joint.Head - drawer, thickness,
width, opening - type.Reveal * 2f ) );
}
Grips( shape, joint, type );
}
static ArchCabinetMount Angle( Vector2 seat, Rotation rotation, string name, float top, float thickness,
float width, float height ) {
return new ArchCabinetMount( name, new Transform( new Vector3( seat.x, seat.y, top ), rotation ),
new Vector3( thickness, width, MathF.Max( 0f, height ) ) );
}
static void Grips( ArchCabinetShape shape, ArchCabinetJoint joint, ArchCabinetType type ) {
if ( !shape.Handles ) {
return;
}
foreach ( var (face, grip) in new[]
{
(ArchCabinetMounts.CornerFront, ArchCabinetMounts.CornerHandle),
(ArchCabinetMounts.CornerFrontUpper, ArchCabinetMounts.CornerHandleUpper)
} ) {
if ( joint.Mount( face ) is not { } front ) {
continue;
}
var reach = MathF.Min( MathF.Max( 2f, type.HandleLength ), front.Space.y * 0.5f );
var down = MathF.Min( 3f, front.Space.z * 0.5f );
var local = front.Local.Position + front.Local.Rotation.Forward * front.Space.x
+ front.Local.Rotation.Left * (front.Space.y - reach - 2f) + Vector3.Down * down;
joint.Mounts.Add( new ArchCabinetMount( grip, new Transform( local, front.Local.Rotation ),
new Vector3( MathF.Max( 0.2f, type.HandleStandoff ), reach, MathF.Max( 0.2f, type.HandleThickness ) ) ) );
}
}
// Module width is a ceiling — runs are filled with standard carcasses plus one narrower filler.
static void Fill( ArchCabinetShape shape, ArchCabinetType type, float module ) {
var division = ArchDivide.AtMost( shape.Run, module );
for ( var index = 0; index < division.Count; index++ ) {
var (from, to) = division.Bay( index );
var cell = new ArchCabinetCell {
Index = index,
Start = shape.Start + shape.Along * from,
End = shape.Start + shape.Along * to,
Foot = shape.CarcassFoot,
Head = shape.CarcassHead,
Depth = shape.Depth,
Slot = Slotted( shape, index, division.Count, to - from, module )
};
Station( shape, cell, type );
shape.Cells.Add( cell );
}
}
static CabinetSlot Slotted( ArchCabinetShape shape, int index, int count, float width, float module ) {
if ( width < module - 0.5f ) {
return CabinetSlot.Filler;
}
if ( index == count - 1 && shape.EndsOpen ) {
return CabinetSlot.End;
}
return index == 0 && shape.StartsOpen ? CabinetSlot.End : CabinetSlot.Module;
}
static void Station( ArchCabinetShape shape, ArchCabinetCell cell, ArchCabinetType type ) {
if ( type.Prefabbed ) {
cell.Mounts.Add( Mount( shape, cell, ArchCabinetMounts.Carcass, 0f, shape.Head, 0f, cell.Width,
shape.Head - shape.Seat, shape.Depth ) );
return;
}
var face = shape.Depth - type.FrontThickness;
var width = cell.Width - type.Reveal * 2f;
var drawer = shape.DrawerRows > 0 ? shape.DrawerHeight * shape.DrawerRows : 0f;
var opening = cell.Head - cell.Foot - drawer;
if ( drawer > 0.5f ) {
cell.Mounts.Add( Mount( shape, cell, ArchCabinetMounts.Drawer, face, cell.Head, type.Reveal, width,
drawer - type.Reveal * 2f, type.FrontThickness ) );
}
if ( type.Splits && type.FrontSplit < opening ) {
var split = cell.Foot + type.FrontSplit;
cell.Mounts.Add( Mount( shape, cell, ArchCabinetMounts.Front, face, split, type.Reveal, width,
split - cell.Foot - type.Reveal * 2f, type.FrontThickness ) );
cell.Mounts.Add( Mount( shape, cell, ArchCabinetMounts.FrontUpper, face, cell.Head - drawer, type.Reveal,
width, cell.Head - drawer - split - type.Reveal * 2f, type.FrontThickness ) );
} else {
cell.Mounts.Add( Mount( shape, cell, ArchCabinetMounts.Front, face, cell.Head - drawer, type.Reveal, width,
opening - type.Reveal * 2f, type.FrontThickness ) );
}
Worktop( shape, cell );
if ( shape.Plinth > 0.01f ) {
cell.Mounts.Add( Mount( shape, cell, ArchCabinetMounts.Plinth, 0f, cell.Foot, 0f,
cell.Width, shape.Plinth, MathF.Max( 1f, shape.Depth - type.PlinthRecess ) ) );
}
Crowning( shape, cell );
Grip( shape, cell, type );
}
static void Worktop( ArchCabinetShape shape, ArchCabinetCell cell ) {
if ( shape.Counter <= 0.01f ) {
return;
}
var at = Vector2.Dot( cell.Start - shape.Start, shape.Along );
var opens = cell.Index == 0 ? shape.TopFrom : at;
var closes = at + cell.Width > shape.Run - 0.05f ? shape.TopTo : at + cell.Width;
var from = MathF.Max( shape.TopFrom, opens ) - at;
var to = MathF.Min( shape.TopTo, closes ) - at;
if ( to - from < 0.05f ) {
return;
}
cell.Mounts.Add( Mount( shape, cell, ArchCabinetMounts.Worktop, -shape.BackOversail, shape.Head, from,
to - from, shape.Counter, shape.TopDepth + shape.BackOversail ) );
}
static void Crowning( ArchCabinetShape shape, ArchCabinetCell cell ) {
if ( shape.Cornice <= 0.01f ) {
return;
}
var at = Vector2.Dot( cell.Start - shape.Start, shape.Along );
var from = (cell.Index == 0 ? shape.CrownFrom : at) - at;
var to = (at + cell.Width > shape.Run - 0.05f ? shape.CrownTo : at + cell.Width) - at;
if ( to - from < 0.05f ) {
return;
}
cell.Mounts.Add( Mount( shape, cell, ArchCabinetMounts.Cornice, 0f, cell.Head + shape.Cornice, from,
to - from, shape.Cornice, shape.CorniceDepth ) );
}
static void Grip( ArchCabinetShape shape, ArchCabinetCell cell, ArchCabinetType type ) {
if ( !shape.Handles ) {
return;
}
foreach ( var name in new[] { ArchCabinetMounts.Front, ArchCabinetMounts.FrontUpper } ) {
if ( cell.Mount( name ) is not { } front ) {
continue;
}
var grip = ArchCabinetMounts.Same( name, ArchCabinetMounts.Front ) ? ArchCabinetMounts.Handle : ArchCabinetMounts.HandleUpper;
var reach = MathF.Min( MathF.Max( 2f, type.HandleLength ), front.Space.y * 0.5f );
var down = MathF.Min( 3f, front.Space.z * 0.5f );
var local = front.Local.Position + front.Local.Rotation.Forward * front.Space.x
+ front.Local.Rotation.Left * (front.Space.y - reach - 2f) + Vector3.Down * down;
cell.Mounts.Add( new ArchCabinetMount( grip, new Transform( local, front.Local.Rotation ),
new Vector3( MathF.Max( 0.2f, type.HandleStandoff ), reach, MathF.Max( 0.2f, type.HandleThickness ) ) ) );
}
}
// Origin at piece TOP, -x -y corner; fills +x(depth) +y(width) -z(height).
static ArchCabinetMount Mount( ArchCabinetShape shape, ArchCabinetCell cell, string name, float back, float top,
float along, float width, float height, float depth ) {
var corner = cell.Start + shape.Facing * back + shape.Along * along;
var rotation = Rotation.LookAt( new Vector3( shape.Facing.x, shape.Facing.y, 0f ), Vector3.Up );
return new ArchCabinetMount( name, new Transform( new Vector3( corner.x, corner.y, top ), rotation ),
new Vector3( MathF.Max( 0f, depth ), MathF.Max( 0f, width ), MathF.Max( 0f, height ) ) );
}
static float Seated( ArchPlan plan, ArchKit kit, ArchRoom room, ArchCabinetPart part, ArchCabinetType type,
CabinetTier tier, float floor ) {
if ( part.Seated ) {
return part.BaseHeight;
}
if ( tier != CabinetTier.Hanging ) {
return floor;
}
if ( CounterBelow( plan, kit, part ) is { } counter ) {
return counter + Splashback;
}
return floor + (type.Datum > 1f ? type.Datum : HungDatum);
}
static float Reached( ArchPlan plan, ArchKit kit, ArchCabinetPart part, ArchCabinetType type, CabinetTier tier,
float seat, float depth ) {
if ( part.Height > 1f ) {
return seat + part.Height;
}
var stood = seat + Stood( part, type, tier );
if ( tier == CabinetTier.Base ) {
return stood;
}
var under = part.Anchor + part.Facing * (depth * 0.5f);
var soffit = ArchPillarSoffit.Covering( plan, kit, under, seat );
if ( soffit >= float.MaxValue ) {
return stood;
}
return tier == CabinetTier.Tall
? MathF.Max( seat + LeastHead, soffit )
: MathF.Min( stood, MathF.Max( seat + LeastHead, soffit ) );
}
static float Stood( ArchCabinetPart part, ArchCabinetType type, CabinetTier tier ) {
var plinth = tier == CabinetTier.Hanging ? 0f : MathF.Max( 0f, type.PlinthHeight );
var counter = tier == CabinetTier.Hanging ? 0f : MathF.Max( 0f, part.TopThickness ?? type.TopThickness );
return plinth + type.Carcass + counter;
}
static float Deep( ArchCabinetPart part, ArchCabinetType type ) {
return part.Depth > 1f ? part.Depth : MathF.Max( 4f, type.Depth );
}
static float Topped( ArchCabinetPart part, ArchCabinetType type, float depth ) {
if ( part.TopDepth > 1f ) {
return part.TopDepth;
}
return type.TopDepth > 1f ? type.TopDepth : depth + MathF.Max( 0f, type.TopOversail );
}
static float Module( ArchCabinetPart part, ArchCabinetType type ) {
return part.ModuleWidth > 1f ? part.ModuleWidth : MathF.Max( 6f, type.Width );
}
static bool Tops( ArchCabinetPart part, ArchCabinetType type, CabinetTier tier ) {
return tier != CabinetTier.Hanging && (part.TopThickness ?? type.TopThickness) > 0.01f;
}
// Lower id keeps the corner; drafts (id <= 0) always keep.
static bool Keeps( ArchCabinetPart one, ArchCabinetPart other ) {
if ( other.Id <= 0 ) {
return false;
}
return one.Id <= 0 || one.Id < other.Id;
}
static void Corners( ArchPlan plan, ArchKit kit, ArchCabinetPart part, ArchCabinetType own, float depth,
float seat, float head, float module, out ArchCabinetEnd first, out ArchCabinetEnd last ) {
first = new ArchCabinetEnd();
last = new ArchCabinetEnd();
if ( plan is null ) {
return;
}
var origin = part.Start;
var along = part.Along;
var facing = part.Facing;
var run = part.Run;
foreach ( var standing in plan.Parts<ArchCabinetPart>() ) {
if ( standing.Id == part.Id || standing.Level != part.Level || !ArchLayerGate.On( standing ) ) {
continue;
}
if ( MathF.Abs( Vector2.Dot( standing.Facing, facing ) ) > 0.1f ) {
continue;
}
var type = ArchCabinetTypes.Find( kit, standing.Type );
if ( type is null ) {
continue;
}
var tier = standing.Tier == CabinetTier.Auto ? type.Tier : standing.Tier;
if ( !Shares( plan, standing, type, tier, seat, head ) || !Beside( standing, origin, facing, depth ) ) {
continue;
}
var reach = Deep( standing, type );
var across = Vector2.Dot( along, standing.Facing );
var offset = Vector2.Dot( origin - standing.Start, standing.Facing );
var back = -offset / across;
var atFirst = MathF.Abs( back ) < module;
var atLast = MathF.Abs( back - run ) < module;
if ( !atFirst && !atLast ) {
continue;
}
var corner = origin + along * back;
if ( !Ends( standing, corner, module ) ) {
continue;
}
var keeper = Keeps( standing, part ) ? type : own;
if ( keeper.Corner == CabinetCorner.None ) {
continue;
}
var front = (reach - offset) / across;
var lip = Tops( standing, type, tier ) ? (Topped( standing, type, reach ) - offset) / across : front;
if ( keeper.Corner == CabinetCorner.Blind ) {
Blind( first, last, back, front, lip, run, atFirst, Keeps( part, standing ) );
continue;
}
var splay = MathF.Max( depth, reach ) * 0.5f;
var mine = reach + splay;
var theirs = depth + splay;
var owns = Keeps( part, standing );
var square = new ArchCabinetSquare( corner, atFirst ? along : -along, facing, mine, theirs, reach,
Tops( standing, type, tier ) ? Topped( standing, type, reach ) : reach,
MathF.Max( 0f, standing.TopBackOversail ?? type.TopBackOversail ) );
if ( atFirst ) {
first.Deeper( back + mine, back + mine, owns ? square : null );
} else {
last.Deeper( run - (back - mine), run - (back - mine), owns ? square : null );
}
}
}
static void Blind( ArchCabinetEnd first, ArchCabinetEnd last, float back, float front, float lip, float run,
bool atFirst, bool keeps ) {
if ( keeps ) {
return;
}
var near = MathF.Min( back, front );
var far = MathF.Max( back, front );
if ( atFirst && far > 0.05f ) {
first.Deeper( far, MathF.Max( far, MathF.Max( back, lip ) ), null );
}
if ( !atFirst && near < run - 0.05f ) {
last.Deeper( run - near, run - MathF.Min( near, MathF.Min( back, lip ) ), null );
}
}
static bool Ends( ArchCabinetPart standing, Vector2 corner, float module ) {
var at = Vector2.Dot( corner - standing.Start, standing.Along );
return MathF.Abs( at ) < module || MathF.Abs( at - standing.Run ) < module;
}
static bool Beside( ArchCabinetPart standing, Vector2 origin, Vector2 facing, float depth ) {
var one = Vector2.Dot( standing.Start - origin, facing );
var other = Vector2.Dot( standing.End - origin, facing );
return MathF.Min( one, other ) < depth - 0.05f && MathF.Max( one, other ) > 0.05f;
}
// Answered without resolving the neighbour — Resolve calls Corners which calls back here.
static bool Shares( ArchPlan plan, ArchCabinetPart standing, ArchCabinetType type, CabinetTier tier, float seat,
float head ) {
var floor = standing.Seated ? standing.BaseHeight : Floor( plan, standing );
var foot = !standing.Seated && tier == CabinetTier.Hanging
? floor + (type.Datum > 1f ? type.Datum : HungDatum)
: floor;
var over = standing.Height > 1f ? foot + standing.Height : foot + Stood( standing, type, tier );
return foot < head - 0.5f && over > seat + 0.5f;
}
static float? CounterBelow( ArchPlan plan, ArchKit kit, ArchCabinetPart part ) {
if ( plan is null ) {
return null;
}
var found = (float?)null;
foreach ( var standing in plan.Parts<ArchCabinetPart>() ) {
if ( standing.Id == part.Id || standing.Level != part.Level || !ArchLayerGate.On( standing ) ) {
continue;
}
if ( Vector2.Dot( standing.Facing, part.Facing ) < 0.9f || !Overlaps( standing, part ) ) {
continue;
}
var type = ArchCabinetTypes.Find( kit, standing.Type );
var tier = standing.Tier == CabinetTier.Auto ? type?.Tier ?? CabinetTier.Base : standing.Tier;
if ( type is null || tier != CabinetTier.Base || (standing.TopThickness ?? type.TopThickness) < 0.01f ) {
continue;
}
var seat = standing.Seated ? standing.BaseHeight : Floor( plan, standing );
var top = standing.Height > 1f ? seat + standing.Height : seat + Stood( standing, type, tier );
if ( found is null || top > found ) {
found = top;
}
}
return found;
}
static float Floor( ArchPlan plan, ArchCabinetPart part ) {
return plan?.HostOf<ArchRoom>( part )?.BaseHeight ?? 0f;
}
static bool Overlaps( ArchCabinetPart one, ArchCabinetPart other ) {
var along = other.Along;
var mine = Vector2.Dot( one.Anchor - other.Anchor, along );
var reach = (one.Run + other.Run) * 0.5f;
var across = Vector2.Dot( one.Anchor - other.Anchor, other.Facing );
return MathF.Abs( mine ) < reach && MathF.Abs( across ) < 24f;
}
}