Editor/Services/ArchRemap.cs

Utility struct that remaps 2D/3D geometry from one rectangular footprint to another. It computes a per-axis scale and translation and provides methods to transform points, boxes, loops (List<Vector2>), paths (List<Vector3>) and ArchCurveNode positions.

Native Interop
using System;
using System.Collections.Generic;
using Sandbox;

namespace Sunless.Architecture;

// One answer to "the shape moved - carry everything standing on it". A move, a corner drag and an
// edge drag all reduce to the same thing: the old bounds became the new ones. Walkways, building
// blueprints and groups all reshape through this arithmetic rather than three copies of it.
public readonly struct ArchRemap
{
	readonly Vector2 from;
	readonly Vector2 scale;
	readonly Vector2 to;

	ArchRemap( Vector2 from, Vector2 scale, Vector2 to )
	{
		this.from = from;
		this.scale = scale;
		this.to = to;
	}

	public static ArchRemap Translation( Vector2 shift ) => new( Vector2.Zero, Vector2.One, shift );

	// A degenerate axis cannot be scaled, so it translates - a flat footprint still follows its drag.
	public static ArchRemap Between( Vector2 fromMin, Vector2 fromMax, Vector2 toMin, Vector2 toMax )
	{
		var span = fromMax - fromMin;
		var wanted = toMax - toMin;

		return new ArchRemap( fromMin, new Vector2( Axis( span.x, wanted.x ), Axis( span.y, wanted.y ) ), toMin );
	}

	static float Axis( float span, float wanted ) => MathF.Abs( span ) < 0.01f ? 1f : wanted / span;

	public Vector2 Of( Vector2 point ) => to + (point - from) * scale;

	public Vector3 Of( Vector3 point )
	{
		var flat = Of( new Vector2( point.x, point.y ) );

		return new Vector3( flat.x, flat.y, point.z );
	}

	// Min/Max pairs must stay ordered: a negative scale would otherwise hand back an inside-out box.
	public void Box( ref Vector2 min, ref Vector2 max )
	{
		var first = Of( min );
		var second = Of( max );

		min = Vector2.Min( first, second );
		max = Vector2.Max( first, second );
	}

	public void Loop( List<Vector2> loop )
	{
		for ( var index = 0; index < loop.Count; index++ )
		{
			loop[index] = Of( loop[index] );
		}
	}

	public void Path( List<Vector3> path )
	{
		for ( var index = 0; index < path.Count; index++ )
		{
			path[index] = Of( path[index] );
		}
	}

	public void Nodes( List<ArchCurveNode> nodes )
	{
		foreach ( var node in nodes )
		{
			node.Position = Of( node.Position );
		}
	}
}