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 );
/// <summary>
/// This plane leaned over, turning about one of its own in-plane axes through its own origin.
/// Onshape's angled plane, minus the part where you pick the edge to hinge on.
///
/// THE HINGE IS ONE OF THE PLANE'S OWN AXES, not a line you point at. Onshape asks for an edge
/// and rotates about that, which is the better answer and needs an axis picker the sketcher does
/// not have yet. What it has is a frame that is already orthonormal and already deterministic,
/// so hinging on X or on Y — with a signed angle — reaches all four of the leans anyone asks for
/// off a given plane. On the three global planes the axes are world axes and this reads exactly
/// as it sounds; on a plane derived from a face they come from the normal alone (see
/// FacePlane.FromPointAndNormal) and which is which is worth looking at rather than predicting.
///
/// THE ORIGIN DOES NOT MOVE, which is what makes offset-then-tilt mean what it looks like: the
/// plane pivots where it sits rather than swinging away from the thing it was offset from.
/// </summary>
public SketchPlane Tilted( float degrees, bool aboutY )
{
if ( degrees == 0f )
return Clone();
var rotate = Xform.Rotate( aboutY ? YAxis : XAxis, degrees * MathF.PI / 180f );
return new SketchPlane( Origin, rotate.TransformDirection( XAxis ), rotate.TransformDirection( 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 );
}