Editor/Services/ArchGridService.cs
using System;
using System.Collections.Generic;
using System.Linq;
using Sandbox;
namespace Sunless.Architecture;
// Sizes always answer; only coordinates check whether snapping is on.
public sealed class ArchGridService {
public const float FinestSize = 0.25f;
// A step this small is no ladder at all, so Snap passes the value through rather than dividing by it.
public const float FreeStep = 0.01f;
// Below this a wall's direction is noise, so no nearest-wall query offers it as a host.
public const float LeastWallLength = 0.5f;
// The angle ladder every authored turn ratchets onto, authored like the size ladder the scene view owns.
// ZERO is the free swing, which is why every reader asks for it rather than testing a flag of its own.
public static float AngleStep {
get => Math.Clamp( EditorCookie.Get( "arch.grid.anglestep", 15f ), 0f, 90f );
set => EditorCookie.Set( "arch.grid.anglestep", Math.Clamp( value, 0f, 90f ) );
}
public static bool Snapping => EditorScene.GizmoSettings.SnapToGrid;
public float BaseSize => Math.Clamp( EditorScene.GizmoSettings.GridSpacing, FinestSize, 128f );
public float Base( float value ) {
return Snapping ? Snap( value, BaseSize ) : value;
}
public Vector2 Base( Vector2 point ) {
return Snapping ? Snap( point, BaseSize ) : point;
}
public Vector3 Base( Vector3 point ) {
return Snapping ? Snap( point, BaseSize ) : point;
}
public float Subgrid( float value, int divisions = 8 ) {
return Snapping ? Snap( value, SubgridSize( divisions ) ) : value;
}
public Vector2 Subgrid( Vector2 point, int divisions = 8 ) {
return Snapping ? Snap( point, SubgridSize( divisions ) ) : point;
}
public Vector3 Subgrid( Vector3 point, int divisions = 8 ) {
return Snapping ? Snap( point, SubgridSize( divisions ) ) : point;
}
public Vector3 CurveControl( Vector3 point ) {
var flat = Base( new Vector2( point.x, point.y ) );
return new Vector3( flat.x, flat.y, Height( point.z ) );
}
// Flat snaps; height does not — terrain heights are measured, not authored.
public Vector3 Draped( Vector3 point ) {
var flat = Base( new Vector2( point.x, point.y ) );
return new Vector3( flat.x, flat.y, point.z );
}
// No second grid for z - the same ladder a plan coordinate lands on.
public float Height( float value ) {
return Base( value );
}
public List<Vector2> Base( IEnumerable<Vector2> points ) {
return points.Select( Base ).ToList();
}
public (Vector2 Min, Vector2 Max) Rectangle( Vector2 first, Vector2 second ) {
var min = Vector2.Min( first, second );
var max = Vector2.Max( first, second );
return (Base( min ), Base( max ));
}
public float SubgridSize( int divisions = 8 ) {
var count = PowerOfTwo( Math.Max( 1, divisions ) );
return MathF.Max( FinestSize, BaseSize / count );
}
public ArchDivision DivideAtMost( float span, float spacing, int divisions = 8 ) {
var length = MathF.Abs( Subgrid( span, divisions ) );
if ( length < FinestSize ) {
return new ArchDivision { Span = 0f, Count = 0 };
}
var unit = SubgridSize( divisions );
var units = Math.Max( 1, (int)MathF.Round( length / unit ) );
var minimum = Math.Max( 1, (int)MathF.Ceiling( length / MathF.Max( unit, spacing ) ) );
var count = minimum;
while ( count < units && units % count != 0 ) {
count++;
}
return new ArchDivision { Span = length, Count = Math.Min( count, units ) };
}
public static float Fine( float value ) {
return Snap( value, FinestSize );
}
public static Vector2 Fine( Vector2 point ) {
return Snap( point, FinestSize );
}
public static Vector3 Fine( Vector3 point ) {
return Snap( point, FinestSize );
}
public static float Least( float value, float least ) {
return MathF.Max( least, Fine( value ) );
}
public static float Angle( float degrees ) {
return Snap( degrees, AngleStep );
}
public static float Snap( float value, float size ) {
return size > FreeStep ? MathF.Round( value / size ) * size : value;
}
// Snap the STEP, not the coordinate — existing shapes keep their authored offset.
public static float Stepped( float from, float to, float size ) {
return from + Snap( to - from, size );
}
public static Vector2 Stepped( Vector2 from, Vector2 to, float size ) {
return new Vector2( Stepped( from.x, to.x, size ), Stepped( from.y, to.y, size ) );
}
static Vector2 Snap( Vector2 point, float size ) {
return new Vector2( Snap( point.x, size ), Snap( point.y, size ) );
}
static Vector3 Snap( Vector3 point, float size ) {
return new Vector3( Snap( point.x, size ), Snap( point.y, size ), Snap( point.z, size ) );
}
static int PowerOfTwo( int value ) {
var result = 1;
while ( result < value ) {
result *= 2;
}
return result;
}
}