Editor/Wall/ArchTrimAffector.cs

Editor utility that updates ArchTrimPart instances which follow other layer shapes. It re-derives a trim part's 3D path from the followed layer's loop and height and assigns the path if it changed, returning counts/booleans for updates.

File Access
using System.Collections.Generic;
using System.Linq;
using Sandbox;

namespace Sunless.Architecture;

// A run that follows another layer's shape is an affector on nothing, but it is a CONSUMER of one - so
// it obeys the same rule: its path is re-derived from where its host stands NOW, on every commit,
// through the one shape answer the host's own generator reads. Drag the cut and the run goes round it.
// A host that stops resolving lets the run keep the path it last had, because a run vanishing because a
// neighbour was deleted is worse than one left standing where it was.
public static class ArchTrimAffector
{
	public static int Resolve( ArchPlan plan, ArchKit kit )
	{
		var kinds = ArchKinds.Load();
		var followed = 0;

		foreach ( var room in plan.AllRooms() )
		{
			foreach ( var trim in plan.Filed<ArchTrimPart>( room, kinds ).Where( trim => trim.Follows ) )
			{
				if ( Refollow( plan, trim ) )
				{
					followed++;
				}
			}
		}

		return followed;
	}

	public static bool Refollow( ArchPlan plan, ArchTrimPart trim )
	{
		var loops = ArchLayerShape.Loops( plan, trim.FollowsId );

		if ( loops.Count == 0 )
		{
			return false;
		}

		var loop = loops[System.Math.Clamp( trim.FollowsLoop, 0, loops.Count - 1 )];
		var height = ArchLayerShape.HeightOf( plan, trim.FollowsId, 0f ) + trim.Lift;
		var path = loop.Select( corner => new Vector3( corner.x, corner.y, height ) ).ToList();

		if ( Same( trim.Path, path ) )
		{
			return false;
		}

		trim.Path = path;
		trim.Closed = true;

		return true;
	}

	// Rewriting an unchanged path every commit would make every rebuild look like an edit to undo.
	static bool Same( IReadOnlyList<Vector3> standing, IReadOnlyList<Vector3> wanted )
	{
		if ( standing.Count != wanted.Count )
		{
			return false;
		}

		for ( var index = 0; index < wanted.Count; index++ )
		{
			if ( (standing[index] - wanted[index]).Length > 0.05f )
			{
				return false;
			}
		}

		return true;
	}
}