Editor/InteriorLayoutBuilder/RoomLayoutTool.CorridorInput.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Editor;
using Sandbox;
namespace ReusableRoomLayout;
public sealed partial class RoomLayoutTool
{
private void HandleCorridorMouse( Vector2 cursor )
{
if ( drawingCorridor )
{
UpdateCorridorDraftPreview( cursor );
if ( Gizmo.WasLeftMousePressed )
{
HandleCorridorDraftClick( cursor );
}
return;
}
if ( !Gizmo.WasLeftMousePressed )
{
return;
}
if ( !TryFindNearestDoor( cursor, out var door, document.Settings.GridSize * 0.65f, roomOnly: true ) )
{
if ( TryFindNearestCorridor( cursor, out var clickedCorridor ) )
{
selectedCorridorId = clickedCorridor.Id;
selectedRoomId = 0;
selectedDoorId = 0;
selectedWindowId = 0;
selectedFloorCutoutId = 0;
}
return;
}
StartCorridorDraft( door );
}
private void HandleCorridorDraftClick( Vector2 cursor )
{
if ( TryFindNearestDoor( cursor, out var door, document.Settings.GridSize * 0.65f, roomOnly: true ) && door.Id != selectedDoorId )
{
TryFinishCorridorDraft( door );
return;
}
AddCorridorDraftBend( cursor );
}
private void StartCorridorDraft( RoomLayoutDoor door )
{
selectedDoorId = door.Id;
selectedRoomId = door.RoomId;
selectedCorridorId = 0;
selectedWindowId = 0;
selectedFloorCutoutId = 0;
drawingCorridor = true;
corridorDraftBendPoints.Clear();
corridorDraftForcedHorizontal = null;
TryGetCorridorDraftLastPoint( out corridorDraftCurrent );
}
private bool TryFinishCorridorDraft( RoomLayoutDoor door )
{
if ( !TryGetDoorAnchor( door.Id, out var endAnchor ) )
{
return false;
}
var width = CorridorWidthForDoors( selectedDoorId, door.Id );
if ( !TryGetCorridorDraftLastPoint( width, out var last ) )
{
return false;
}
var endTarget = CorridorApproachPoint( endAnchor, width );
var bends = CleanCorridorDraftBends();
if ( !IsOrthogonalSegment( last, endTarget ) )
{
var corner = PreferredFinalCorridorCorner( last, endTarget );
if ( Vector2.DistanceBetween( last, corner ) < 1.0f ||
Vector2.DistanceBetween( corner, endTarget ) < 1.0f )
{
return false;
}
bends.Add( RoomLayoutPoint.FromVector( corner ) );
}
var createdCorridor = new RoomLayoutCorridor
{
Id = document.AllocateId(),
Floor = activeFloor,
StartDoorId = selectedDoorId,
EndDoorId = door.Id,
Width = width,
ManualPath = true,
BendPoints = CleanCorridorBends( bends )
};
PushLayoutUndo();
document.Corridors.Add( createdCorridor );
selectedCorridorId = createdCorridor.Id;
CancelCorridorDraft();
CommitLayoutChange();
return true;
}
private List<RoomLayoutPoint> CleanCorridorBends( IEnumerable<RoomLayoutPoint> bends )
{
var points = bends
.Select( bend => bend.Vector )
.ToList();
SimplifyCorridorPath( points );
return points.Select( RoomLayoutPoint.FromVector ).ToList();
}
private void AddCorridorDraftBend( Vector2 cursor )
{
if ( !TryGetCorridorDraftLastPoint( out var last ) )
{
return;
}
var next = ConstrainCorridorPoint( last, cursor, corridorDraftForcedHorizontal );
if ( Vector2.DistanceBetween( last, next ) < 1.0f )
{
ToggleCorridorDraftDirection();
UpdateCorridorDraftPreview( cursor );
return;
}
corridorDraftBendPoints.Add( RoomLayoutPoint.FromVector( next ) );
corridorDraftCurrent = next;
corridorDraftForcedHorizontal = null;
}
private void UpdateCorridorDraftPreview( Vector2 cursor )
{
if ( !TryGetCorridorDraftLastPoint( out var last ) )
{
corridorDraftCurrent = cursor;
return;
}
corridorDraftCurrent = ConstrainCorridorPoint( last, cursor, corridorDraftForcedHorizontal );
}
private bool TryGetCorridorDraftLastPoint( out Vector2 point )
{
return TryGetCorridorDraftLastPoint( CorridorDraftWidth(), out point );
}
private bool TryGetCorridorDraftLastPoint( float width, out Vector2 point )
{
if ( corridorDraftBendPoints.Count > 0 )
{
point = corridorDraftBendPoints[^1].Vector;
return true;
}
point = default;
if ( !TryGetDoorAnchor( selectedDoorId, out var startAnchor ) )
{
return false;
}
point = CorridorApproachPoint( startAnchor, width );
return true;
}
private float CorridorDraftWidth()
{
var selectedDoor = document.FindDoor( selectedDoorId );
var width = MathF.Max( 1.0f, document.Settings.DefaultCorridorWidth );
return selectedDoor is null || selectedDoor.Width <= 0.0f
? width
: MathF.Min( width, selectedDoor.Width );
}
private List<RoomLayoutPoint> CleanCorridorDraftBends()
{
return CleanCorridorBends( corridorDraftBendPoints );
}
private void CancelCorridorDraft( bool clearSelectedDoor = true )
{
drawingCorridor = false;
corridorDraftBendPoints.Clear();
corridorDraftCurrent = default;
corridorDraftForcedHorizontal = null;
if ( clearSelectedDoor )
{
selectedDoorId = 0;
}
}
private void ToggleCorridorDraftDirection()
{
corridorDraftForcedHorizontal = corridorDraftForcedHorizontal.HasValue
? !corridorDraftForcedHorizontal.Value
: PreferredInstantTurnAxis();
}
private bool PreferredInstantTurnAxis()
{
if ( TryGetCorridorDraftPreviousPoint( out var previous ) &&
TryGetCorridorDraftLastPoint( out var last ) &&
Vector2.DistanceBetween( previous, last ) > 1.0f )
{
return !IsHorizontalSegment( previous, last );
}
if ( TryGetDoorAnchor( selectedDoorId, out var start ) )
{
return MathF.Abs( start.Normal.y ) > MathF.Abs( start.Normal.x );
}
return true;
}
private bool TryGetCorridorDraftPreviousPoint( out Vector2 point )
{
point = default;
if ( corridorDraftBendPoints.Count > 1 )
{
point = corridorDraftBendPoints[^2].Vector;
return true;
}
if ( corridorDraftBendPoints.Count == 1 && TryGetDoorAnchor( selectedDoorId, out var start ) )
{
point = CorridorApproachPoint( start, CorridorDraftWidth() );
return true;
}
return false;
}
private Vector2 ConstrainCorridorPoint( Vector2 from, Vector2 to, bool? forcedHorizontal = null )
{
var dx = MathF.Abs( to.x - from.x );
var dy = MathF.Abs( to.y - from.y );
var horizontal = forcedHorizontal ?? PreferredCorridorDraftAxis( from, dx, dy );
return horizontal
? new Vector2( to.x, from.y )
: new Vector2( from.x, to.y );
}
private bool PreferredCorridorDraftAxis( Vector2 from, float dx, float dy )
{
if ( MathF.Abs( dx - dy ) > 0.001f )
{
return dx > dy;
}
if ( TryGetCorridorDraftPreviousPoint( out var previous ) &&
Vector2.DistanceBetween( previous, from ) > 1.0f )
{
return !IsHorizontalSegment( previous, from );
}
return true;
}
private Vector2 PreferredFinalCorridorCorner( Vector2 last, Vector2 endPortal )
{
if ( corridorDraftForcedHorizontal.HasValue )
{
return corridorDraftForcedHorizontal.Value
? new Vector2( endPortal.x, last.y )
: new Vector2( last.x, endPortal.y );
}
if ( TryGetCorridorDraftPreviousPoint( out var previous ) &&
Vector2.DistanceBetween( previous, last ) > 1.0f )
{
return IsHorizontalSegment( previous, last )
? new Vector2( endPortal.x, last.y )
: new Vector2( last.x, endPortal.y );
}
var dx = MathF.Abs( endPortal.x - last.x );
var dy = MathF.Abs( endPortal.y - last.y );
return dx >= dy
? new Vector2( endPortal.x, last.y )
: new Vector2( last.x, endPortal.y );
}
private static bool IsOrthogonalSegment( Vector2 a, Vector2 b )
{
return a.x.AlmostEqual( b.x ) || a.y.AlmostEqual( b.y );
}
private static bool IsHorizontalSegment( Vector2 a, Vector2 b )
{
return MathF.Abs( b.x - a.x ) >= MathF.Abs( b.y - a.y );
}
}