Editor/Effigy/Sketch/SketchPlane.cs
using System;

namespace Effigy;

/// <summary>
/// The plane a sketch lives on: an origin and two in-plane axes, with the normal derived from
/// them.
///
/// Sketch entities are stored in 2D plane coordinates, never in world space. That is the whole
/// reason this type exists — a sketch that stored world positions would silently break the moment
/// its plane moved, and "move the sketch plane and everything on it follows" is a thing people
/// expect from CAD and would notice immediately if it were missing.
/// </summary>
public sealed class SketchPlane
{
	public Vec3 Origin;
	public Vec3 XAxis;
	public Vec3 YAxis;

	public SketchPlane( Vec3 origin, Vec3 xAxis, Vec3 yAxis )
	{
		Origin = origin;
		XAxis = xAxis.Normal;
		YAxis = yAxis.Normal;
	}

	public Vec3 Normal => Vec3.Cross( XAxis, YAxis ).Normal;

	public static SketchPlane XY => new( Vec3.Zero, new Vec3( 1, 0, 0 ), new Vec3( 0, 1, 0 ) );
	public static SketchPlane XZ => new( Vec3.Zero, new Vec3( 1, 0, 0 ), new Vec3( 0, 0, 1 ) );
	public static SketchPlane YZ => new( Vec3.Zero, new Vec3( 0, 1, 0 ), new Vec3( 0, 0, 1 ) );

	/// <summary>A plane parallel to this one, offset along the normal. Onshape's offset plane.</summary>
	public SketchPlane Offset( float distance ) =>
		new( Origin + Normal * distance, XAxis, YAxis );

	public Vec3 ToWorld( Vec2 p ) => Origin + XAxis * p.x + YAxis * p.y;

	/// <summary>Project a world point onto the plane. Anything off the plane loses its offset —
	/// that is intended, this is a projection and not an inverse.</summary>
	public Vec2 ToPlane( Vec3 p )
	{
		var d = p - Origin;
		return new Vec2( Vec3.Dot( d, XAxis ), Vec3.Dot( d, YAxis ) );
	}

	public SketchPlane Clone() => new( Origin, XAxis, YAxis );
}