Editor/Effigy/MeshSection.cs
using System;
using System.Collections.Generic;

namespace Effigy;

/// <summary>
/// Where a mesh meets a plane, as line segments.
///
/// This exists for one job: showing the FOOTPRINT another body leaves on a face. Effigy does not
/// union bodies, so an extrude standing on a slab is two separate solids that happen to overlap,
/// and the slab's top face is one uninterrupted rectangle with no idea anything is sitting on it.
/// Outlining only that rectangle makes a face with a block on it look completely clear.
///
/// Two cases, and both turn up constantly because they are the two ways bodies get stacked:
/// a solid that PASSES THROUGH the plane, whose faces cross it and produce a chord each; and one
/// that SITS ON it, whose bottom face lies in the plane and produces its own outline. Handling
/// only the first would miss every part built by sketching on a face, which is most of them.
/// </summary>
public static class MeshSection
{
	/// <summary>
	/// Every segment where <paramref name="mesh"/> meets the plane through
	/// <paramref name="planeOrigin"/> with <paramref name="planeNormal"/>.
	/// </summary>
	public static List<(Vec3 A, Vec3 B)> CrossSection( PolyMesh mesh, Vec3 planeOrigin, Vec3 planeNormal,
		float tolerance = 1e-4f )
	{
		var segments = new List<(Vec3, Vec3)>();

		if ( mesh is null || mesh.Faces.Count == 0 )
			return segments;

		var normal = planeNormal.Normal;

		foreach ( var face in mesh.Faces )
		{
			if ( face.Count < 3 )
				continue;

			var distances = new float[face.Count];
			var above = false;
			var below = false;

			for ( var i = 0; i < face.Count; i++ )
			{
				distances[i] = Vec3.Dot( mesh.Positions[face.Indices[i]] - planeOrigin, normal );

				if ( distances[i] > tolerance )
					above = true;
				else if ( distances[i] < -tolerance )
					below = true;
			}

			var coplanar = !above && !below;

			// Sitting flat ON the plane: the face's own edges ARE the footprint.
			if ( coplanar )
			{
				for ( var i = 0; i < face.Count; i++ )
				{
					segments.Add( (mesh.Positions[face.Indices[i]],
						mesh.Positions[face.Indices[(i + 1) % face.Count]]) );
				}

				continue;
			}

			// STRICTLY BOTH SIDES, or this face does not cross the plane - it only touches it.
			// A block standing on a slab touches with all four of its side walls, each along its
			// bottom edge, and every one of those edges is already an edge of the coplanar bottom
			// face. Without this the whole footprint gets drawn twice.
			if ( !above || !below )
				continue;

			// Passing through it: collect where the face's edges cross, which for a convex face is
			// exactly two points and therefore one chord.
			var crossings = new List<Vec3>( 2 );

			for ( var i = 0; i < face.Count; i++ )
			{
				var a = mesh.Positions[face.Indices[i]];
				var b = mesh.Positions[face.Indices[(i + 1) % face.Count]];
				var da = distances[i];
				var db = distances[(i + 1) % face.Count];

				// A corner sitting on the plane is reported once, by the edge that arrives at it -
				// counting it twice would leave a zero-length segment behind.
				if ( MathF.Abs( da ) <= tolerance )
				{
					crossings.Add( a );
					continue;
				}

				if ( MathF.Abs( db ) <= tolerance || da * db > 0f )
					continue;

				crossings.Add( a + (b - a) * (da / (da - db)) );
			}

			for ( var i = 0; i + 1 < crossings.Count; i += 2 )
			{
				if ( (crossings[i + 1] - crossings[i]).LengthSquared > tolerance * tolerance )
					segments.Add( (crossings[i], crossings[i + 1]) );
			}
		}

		return segments;
	}
}