Editor/Cabinet/ArchCabinets.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace Sunless.Architecture;
public enum CabinetStanding {
Backed,
Free
}
public static class ArchCabinets {
public static ArchCabinetPart Place( ArchPlan plan, ArchKit kit, ArchFixtureStation? at, ArchCabinetPart draft, out ArchRoom host ) {
host = null;
if ( plan is null || draft is null || at is not { } station ) {
return null;
}
host = station.Room;
var thickness = station.Wall.Thickness > 0f ? station.Wall.Thickness : kit.WallThickness;
var facing = Inward( station );
return Drafted( plan, host, station.Room.Floor, station.Centre + facing * (thickness * 0.5f), facing,
station.Width, draft );
}
public static ArchCabinetPart Free( ArchPlan plan, ArchRoom room, ArchGridService grid, Vector2 from, Vector2 to,
int quarters, float module, ArchCabinetPart draft ) {
if ( plan is null || room is null || grid is null || draft is null ) {
return null;
}
var facing = Turned( Inward( plan, room, from ), quarters );
var along = new Vector2( -facing.y, facing.x );
var travelled = Vector2.Dot( to - from, along );
var least = MathF.Max( ArchFixtureAnchor.Least, grid.Subgrid( module, 4 ) );
if ( MathF.Abs( travelled ) < grid.SubgridSize() ) {
return Drafted( plan, room, room.Floor, from, facing, least, draft );
}
return Drafted( plan, room, room.Floor, from + along * (travelled * 0.5f), facing,
MathF.Max( least, MathF.Abs( travelled ) ), draft );
}
public static Vector2 Turned( Vector2 facing, int quarters ) {
return ((quarters % 4 + 4) % 4) switch {
1 => new Vector2( -facing.y, facing.x ),
2 => -facing,
3 => new Vector2( facing.y, -facing.x ),
_ => facing
};
}
public static ArchCabinetPart Continues( ArchPlan plan, ArchRoom room, ArchCabinetPart placed, float module ) {
if ( plan is null || room is null || placed is null ) {
return null;
}
var facing = placed.Facing;
var along = placed.Along;
var slack = MathF.Max( 1f, module * 0.5f );
foreach ( var standing in plan.Filed<ArchCabinetPart>( room ) ) {
if ( ReferenceEquals( standing, placed ) || standing.Level != placed.Level ) {
continue;
}
if ( Vector2.Dot( standing.Facing, facing ) < 0.999f || !Alike( standing, placed ) ) {
continue;
}
// On the same back line as well as looking the same way, or two runs on opposite sides of a partition
// - or one standing off the wall as an island - are read as continuing each other.
if ( MathF.Abs( Vector2.Dot( standing.Anchor - placed.Anchor, facing ) ) > 1f ) {
continue;
}
if ( MathF.Abs( Vector2.Dot( standing.Anchor - placed.Anchor, along ) )
> (standing.Run + placed.Run) * 0.5f + slack ) {
continue;
}
return standing;
}
return null;
}
public static void Absorb( ArchCabinetPart standing, ArchCabinetPart placed ) {
var along = standing.Along;
var offset = Vector2.Dot( placed.Anchor - standing.Anchor, along );
var from = MathF.Min( -standing.Run * 0.5f, offset - placed.Run * 0.5f );
var to = MathF.Max( standing.Run * 0.5f, offset + placed.Run * 0.5f );
standing.Anchor += along * ((from + to) * 0.5f);
standing.Width = to - from;
}
public static ArchCabinetPart Joined( ArchCabinetPart standing, ArchCabinetPart placed ) {
var grown = Copy( standing );
Absorb( grown, placed );
return grown;
}
static bool Alike( ArchCabinetPart standing, ArchCabinetPart placed ) {
if ( !string.Equals( standing.Type, placed.Type, StringComparison.OrdinalIgnoreCase ) || standing.Tier != placed.Tier ) {
return false;
}
return standing.Seated == placed.Seated
&& (!standing.Seated || MathF.Abs( standing.BaseHeight - placed.BaseHeight ) < 0.01f);
}
public static ArchCabinetShape Shaped( ArchPlan plan, ArchKit kit, ArchCabinetPart sketch, ArchRoom room ) {
if ( sketch is null || room is null ) {
return null;
}
var shape = ArchCabinetShape.Resolve( plan, kit, room, plan?.OwnerOf( room ), sketch );
return shape.Standing ? shape : null;
}
public static bool Preview( ArchPlan plan, ArchKit kit, ArchCabinetPart sketch, ArchRoom room ) {
if ( Shaped( plan, kit, sketch, room ) is not { } shape ) {
return false;
}
var building = plan?.OwnerOf( room );
var lift = ArchAnswers.Load().Lift( plan, building, kit );
var seat = shape.Seat + lift;
var head = shape.Head + lift;
if ( shape.Cells.Count > 0 ) {
ArchGhost.Prism( shape.Outline(), seat, head );
}
foreach ( var joint in shape.Joints ) {
ArchGhost.Prism( joint.Loop, joint.Foot + lift, joint.Head + lift );
}
foreach ( var cell in shape.Cells.Skip( 1 ) ) {
ArchGhost.Path( new List<Vector3>
{
new( cell.Start.x, cell.Start.y, seat ),
new( cell.Start.x, cell.Start.y, head )
}, false );
}
ArchGhost.Note( new Vector3( shape.End.x, shape.End.y, head ), shape.Cells.Count > 0
? $"{shape.Type.Name} — {shape.Cells.Count} × {shape.Cells[0].Width:0} at {seat:0}"
: $"{shape.Type.Name} — corner only at {seat:0}" );
return true;
}
static ArchCabinetPart Drafted( ArchPlan plan, ArchRoom room, int level, Vector2 anchor, Vector2 facing,
float width, ArchCabinetPart draft ) {
return new ArchCabinetPart {
Id = plan.AllocateId(),
Name = $"Cabinets{plan.Filed<ArchCabinetPart>( room ).Count() + 1}",
Level = level,
Anchor = anchor,
Outward = facing,
Width = width,
Tier = draft.Tier,
Type = draft.Type,
ModuleWidth = draft.ModuleWidth,
Depth = draft.Depth,
TopDepth = draft.TopDepth,
Height = draft.Height,
Interior = draft.Interior,
Shelves = draft.Shelves,
DrawerRows = draft.DrawerRows,
Handles = draft.Handles,
TopThickness = draft.TopThickness,
TopEndOversail = draft.TopEndOversail,
TopBackOversail = draft.TopBackOversail,
MaterialGroup = draft.MaterialGroup,
Fittings = draft.Fittings?.Select( Copy ).ToList() ?? new List<ArchCabinetFitting>()
};
}
// Inverted — cabinets face INTO the room
static Vector2 Inward( ArchFixtureStation station ) {
var outward = station.Outward;
return outward.Length < 0.01f ? new Vector2( 1f, 0f ) : -outward.Normal;
}
// Free-standing: face out of nearest wall, toward the aim
static Vector2 Inward( ArchPlan plan, ArchRoom room, Vector2 point ) {
var facing = new Vector2( 1f, 0f );
var nearest = float.MaxValue;
foreach ( var wall in plan.Filed<ArchWall>( room ) ) {
var length = wall.Length;
if ( length < 0.5f ) {
continue;
}
var along = Math.Clamp( Vector2.Dot( point - wall.Start, wall.Direction ), 0f, length );
var offset = point - wall.PointAt( along );
if ( offset.Length >= nearest ) {
continue;
}
nearest = offset.Length;
facing = Vector2.Dot( wall.Normal, offset ) < 0f ? -wall.Normal : wall.Normal;
}
return facing;
}
static ArchCabinetPart Copy( ArchCabinetPart part ) {
return new ArchCabinetPart {
Id = part.Id,
Name = part.Name,
Level = part.Level,
Anchor = part.Anchor,
Outward = part.Outward,
Width = part.Width,
Tier = part.Tier,
Type = part.Type,
ModuleWidth = part.ModuleWidth,
Depth = part.Depth,
TopDepth = part.TopDepth,
Height = part.Height,
BaseHeight = part.BaseHeight,
Seated = part.Seated,
Interior = part.Interior,
Shelves = part.Shelves,
DrawerRows = part.DrawerRows,
Handles = part.Handles,
TopThickness = part.TopThickness,
TopEndOversail = part.TopEndOversail,
TopBackOversail = part.TopBackOversail,
MaterialGroup = part.MaterialGroup,
Palette = part.Palette,
Fittings = part.Fittings?.Select( Copy ).ToList() ?? new List<ArchCabinetFitting>()
};
}
static ArchCabinetFitting Copy( ArchCabinetFitting fitting ) {
return new ArchCabinetFitting {
Mount = fitting.Mount,
Front = fitting.Front,
Prefab = fitting.Prefab,
Fits = fitting.Fits,
Anchor = fitting.Anchor,
AnchorAlong = fitting.AnchorAlong,
AnchorTurn = fitting.AnchorTurn
};
}
}