Euclid 0.50.0

Prefix Reserved
dotnet add package Euclid --version 0.50.0
                    
NuGet\Install-Package Euclid -Version 0.50.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Euclid" Version="0.50.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Euclid" Version="0.50.0" />
                    
Directory.Packages.props
<PackageReference Include="Euclid" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Euclid --version 0.50.0
                    
#r "nuget: Euclid, 0.50.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Euclid@0.50.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Euclid&version=0.50.0
                    
Install as a Cake Addin
#tool nuget:?package=Euclid&version=0.50.0
                    
Install as a Cake Tool

Logo

Euclid

Euclid on nuget.org Build Status Docs Build Status Test Status license code size

A comprehensive 2D and 3D geometry library in F# without dependencies, designed for precision engineering and computational design.
It runs on JavaScript too.

Features

While most 3D geometry libraries just use a generic type ( often called Vec3 or similar) containing three floats for both points and vectors,
Euclid makes a distinction to preserve semantic meaning and avoid accidental misuse.
A point represents a position in space, while a vector represents a direction and magnitude.
Even unitized vectors are a separate type. They are guaranteed to have a length of 1.0.
This helps to skip checks for zero length vectors in many operations.

🎯 Core Geometry Types

  • Points (Pt, Pnt), Vectors (Vc, Vec), Unit Vectors (UnitVc, UnitVec)
  • Lines, Planes, Boxes, Bounding Boxes, Polylines
  • Rotations, Quaternions, 4x4 and rigid orthonormal 4x3 matrices

Key Characteristics

  • Zero dependencies: lightweight and self-contained
  • Double precision: designed for CAD/manufacturing accuracy
  • Immutable types: all primitive types are immutable for safety and ease of reasoning
  • Cross-platform: compiles to .NET, JavaScript, TypeScript, Rust, Python via Fable
  • Performance Focus: All small types are structs, functions are often inline and try to minimize the allocation.
  • Interoperability: Can be used with Rhino3D, Revit, or any .NET environment, and also in JavaScript/TypeScript projects via Fable.

Coordinate System

This library uses a right-handed coordinate system with the Z-axis pointing up.

Same as in: Rhino3D, Blender, SketchUp, Revit, AutoCAD
Different from: Unity, Unreal Engine, Maya

This choice aligns with industry-standard CAD and architectural software.

Design Philosophy

Points vs Vectors

  • Points (Pt, Pnt): Positions in space
  • Vectors (Vc, Vec): Directions and displacements

When a 4x4 transformation matrix is applied:

  • Points: Undergo full transformation (rotation, scaling, translation)
  • Vectors: Only rotate and scale (no translation)

This follows homogeneous coordinate conventions where vectors have w=0.

A vector in Euclid is just a direction with a magnitude. It has no location in space. Therefore transformations that depend on a location never apply to vectors:

  • A vector cannot be translated. Translating a direction is meaningless; it stays the same vector.
  • A vector cannot be rotated around a center point. Only location-bearing types (points, lines, planes, rectangles, boxes, polylines) can rotate around a center. Vectors only rotate around the origin (a pure rotation, e.g. Vec.rotate, Vec.rotateOnZ).

The API enforces this: vector types (Vc, Vec, UnitVc, UnitVec) deliberately offer no translate or rotateWithCenter members. (The two members that violated this rule, Vec.rotateWithCenter and UnitVec.rotateWithCenter, are scheduled for removal.)

Naming Conventions

Core Types (2D and 3D):

Type 2D 3D
Point Pt Pnt
Vector Vc Vec
Unit Vector UnitVc UnitVec
Line Line2D Line3D
Polyline Polyline2D Polyline3D
Rectangle Rect2D Rect3D
Bounding Rect/Box BRect BBox

3D-only Types:

Type Description
PPlane Parametrized plane (origin + X/Y/Z axes)
NPlane Normal plane (origin + normal vector)
Box Oriented 3D box (origin + 3 axis vectors)
Matrix 4x4 transformation matrix
RigidMatrix 4x3 rigid transformation (rotation + translation only)
Quaternion Unitized quaternion for 3D rotations
Rotation2D 2D rotation stored as sine/cosine pair

Function Patterns

Functions are available in multiple forms:

// Static module function (lowercase, pipeable)
let normalized = Vec.unitize myVector
let moved = Pnt.translate myVec myPoint

// Instance method/property (uppercase)
let normalized = myVector.Unitized
let moved = myPoint.Transform myMatrix

// Pipeline style
myPoint
|> Pnt.translate (Vec(1, 0, 0))
|> Pnt.rotateOnZDeg 45.0
|> Pnt.transform myMatrix

Custom Operators

a + b           // Point + Vector = Point, Vector + Vector = Vector
a - b           // Point - Point = Vector, Point - Vector = Point
a * 2.0         // Scale vector or point
a / 2.0         // Divide (with zero-check)
a *** b         // Dot product for vectors, matrix multiplication for matrices
-a              // Negate a vector

API Documentation

Full API Reference: goswinr.github.io/Euclid

Platform Support

Thanks to Fable, Euclid can be used not only on .NET but also in JavaScript, TypeScript, Rust, and Python.

Development

Use of AI and LLMs

All core function are are written by hand to ensure performance and correctness.
However, AI tools have been used for code review, typo and grammar checking in documentation
and to generate not all but many of the tests.

Prerequisites

  • .NET SDK 10.0 or later (to have scoped warnings available)
  • Node.js (only needed to run the .NET tests in JavaScript and TypeScript via Fable.Mocha)

Building from Source

git clone https://github.com/goswinr/Euclid.git
cd Euclid

To build the library and the tests for .NET

dotnet build --configuration Release

for JavaScript/TypeScript (compiles the .NET code to JavaScript via Fable)

cd Test
npm i
npm run build
cd ..

Testing

Tests run on both .NET and JavaScript with TypeScript build verification.

.NET Testing

run the tests from root. (not from within the Test folder)

dotnet run --project ./Test/Test.fsproj

JavaScript Testing

run

cd Test
npm i
npm test
cd ..

The test suite ensures cross-platform compatibility and verifies TypeScript type definitions.

Benchmarks

Some more complex algorithms are benchmarked in both .NET and Node JavaScript. See Test/Benchmarks/README.md.

Contributing

Contributions are welcome!

Changelog

See CHANGELOG.md for version history.

Euclid.Rhino - Rhino3D integration

License

MIT

Installation

Add Euclid to your F# project via NuGet:

dotnet add package Euclid

Or in F# scripting:

#r "nuget: Euclid"

Quick Start

open Euclid

// Create 3D points and vectors
let point1 = Pnt(1.0, 2.0, 3.0)
let point2 = Pnt(4.0, 5.0, 6.0)
let vector = Vec(1.0, 1.0, 0.0)

// Calculate distance
let distance = point1.DistanceTo point2

// Create and use unit vectors
let unitVec = vector.Unitized  // returns a UnitVec

// Transform with 4x4 matrix
let matrix =
    Matrix.createShear(3.0, 0, 0, 0, 0, 0)
    *** // Combine transformations
    Matrix.createRotationZ 45

point1
|> Pnt.translate vector
|> Pnt.scale 3.0
|> Pnt.transform matrix

Examples

Points and Vectors (2D)

open Euclid

// 2D points and vectors
let a = Pt(1.0, 2.0)
let b = Pt(4.0, 6.0)

// Distance between points
let dist = a.DistanceTo b              // 5.0

// Midpoint
let mid = Pt.midPt a b                  // Pt(2.5, 4.0)

// 2D vectors
let v = Vc(3.0, 4.0)
let len = v.Length                       // 5.0
let half = v.Half                        // Vc(1.5, 2.0)
let perp = v.Rotate90CCW                 // perpendicular vector

// Point + vector arithmetic
let moved = a + v                        // Pt(4.0, 6.0)
let diff = b - a                         // Vc(3.0, 4.0) : point - point = vector

// Unit vectors (guaranteed length 1.0)
let dir = v.Unitized                     // UnitVc(0.6, 0.8)
let angle = v.Direction360               // angle in degrees from X-axis

// Dot and cross product
let dot = v.Dot(Vc(1.0, 0.0))           // 3.0
let cross = v.Cross(Vc(1.0, 0.0))       // -4.0 (signed area in 2D)

Points and Vectors (3D)

open Euclid

// 3D points and vectors
let p1 = Pnt(1.0, 2.0, 3.0)
let p2 = Pnt(4.0, 6.0, 3.0)

// Distance, midpoint, lerp
let dist = p1.DistanceTo p2             // 5.0
let mid  = Pnt.midPt p1 p2              // Pnt(2.5, 4.0, 3.0)
let lerp = Pnt.divPt(p1, p2, 0.25)     // 25% from p1 towards p2

// 3D vectors
let v1 = Vec(1.0, 0.0, 0.0)
let v2 = Vec(0.0, 1.0, 0.0)

// Cross product and dot product
let normal = v1.Cross v2                 // Vec(0, 0, 1)
let dot = v1.Dot v2                      // 0.0

// Angle between vectors
let angle = Vec.angle180 v1 v2           // 90.0 degrees

// Unit vectors
let dir = Vec(3.0, 4.0, 0.0).Unitized   // UnitVec(0.6, 0.8, 0.0)
let isPerp = dir.IsPerpendicularTo UnitVec.Zaxis  // true

// Convert between 2D and 3D
let pt2d = p1.AsPt                       // Pt(1.0, 2.0) - drops Z
let pt3d = Pt(1.0, 2.0).WithZ 5.0       // Pnt(1.0, 2.0, 5.0)

Lines

open Euclid

// Create lines from points
let ln2d = Line2D(Pt(0, 0), Pt(10, 0))
let ln3d = Line3D(Pnt(0, 0, 0), Pnt(10, 5, 3))

// Or from coordinates
let ln = Line2D(0.0, 0.0, 10.0, 5.0)

// Line properties
let len    = ln2d.Length                 // 10.0
let midPt  = ln2d.Mid                   // Pt(5, 0)
let dir    = ln3d.Direction             // UnitVec of line direction
let tang   = ln3d.Tangent               // UnitVec (same as Direction)

// Evaluate at parameter (0.0 = start, 1.0 = end)
let quarterPt = ln2d.EvaluateAt 0.25    // Pt(2.5, 0)

// Extend, shrink, reverse
let longer  = ln2d.Extend(2.0, 3.0)     // extend 2 at start, 3 at end
let shorter = ln2d.Shrink(1.0, 1.0)     // shrink 1 from each end
let flipped = ln2d.Reversed              // swap start and end

// Closest point queries
let testPt = Pt(5.0, 3.0)
let closest = ln2d.ClosestPoint testPt   // Pt(5, 0) - clamped to segment
let param = ln2d.ClosestParameter testPt // 0.5

// Line-line relationships
let other = Line2D(Pt(5, -5), Pt(5, 5))
let isParallel = ln2d.IsParallelTo other       // false
let isPerp = ln2d.IsPerpendicularTo other      // true

// Intersection (returns option)
let hit = Line2D.tryIntersect ln2d other        // Some (Pt(5, 0))

// Offset a line
let offsetLn = Line2D.offset 2.0 ln2d   // offset by 2.0 to the left

// Move a line
let movedLn = ln2d.Move(Vc(0.0, 5.0))   // translate by vector

Line Intersections (Discriminated Unions)

For detailed intersection analysis, the XLine2D and XLine3D modules return discriminated union types that let you handle every case explicitly:

open Euclid

let lineA = Line2D(Pt(0, 0), Pt(10, 0))
let lineB = Line2D(Pt(5, -5), Pt(5, 5))

// Finite segment intersection - returns XPt discriminated union
match XLine2D.getIntersection(lineA, lineB) with
| XLine2D.XPt.Intersect pt       -> printfn $"Lines cross at {pt}"
| XLine2D.XPt.Apart              -> printfn "Segments don't reach each other"
| XLine2D.XPt.Parallel           -> printfn "Lines are parallel"
| XLine2D.XPt.TooShortA          -> printfn "Line A is too short"
| XLine2D.XPt.TooShortB          -> printfn "Line B is too short"
| XLine2D.XPt.TooShortBoth       -> printfn "Both lines are too short"

// Ray intersection (extends lines infinitely)
match XLine2D.getRayIntersection(lineA, lineB) with
| XLine2D.XRay.Intersect pt       -> printfn $"Rays meet at {pt}"
| XLine2D.XRay.Parallel           -> printfn "Rays are parallel"
| _                               -> ()

// 3D line relationships include skew detection
let ln3A = Line3D(Pnt(0, 0, 0), Pnt(10, 0, 0))
let ln3B = Line3D(Pnt(5, 0, 5), Pnt(5, 10, 5))

match XLine3D.getIntersection(ln3A, ln3B) with
| XLine3D.XPnt.Intersect pt       -> printfn $"Lines intersect at {pt}"
| XLine3D.XPnt.Skew (ptA, ptB, d) -> printfn $"Closest approach: {d} between {ptA} and {ptB}"
| XLine3D.XPnt.Apart              -> printfn "Segments don't reach each other"
| XLine3D.XPnt.Parallel           -> printfn "Lines are parallel"
| _                               -> ()

Planes

open Euclid

// Normal plane (origin + normal direction)
let npl = NPlane(Pnt(0, 0, 5), UnitVec.Zaxis)

// Signed distance from plane (positive = same side as normal)
let d = npl.DistanceToPnt (Pnt(3, 4, 8))         // 3.0
let d2 = npl.DistanceToPnt (Pnt(3, 4, 2))        // -3.0

// Project a point onto the plane
let proj = npl.ClosestPoint (Pnt(3, 4, 8))       // Pnt(3, 4, 5)

// Angle between plane and vector (0 = parallel, 90 = perpendicular)
let ang = npl.Angle90ToVec (Vec(1, 0, 0))         // 0.0 (vector lies in plane)

// Parametrized plane (full coordinate frame with X, Y, Z axes)
let ppl = PPlane.createThreePoints
            (Pnt(0, 0, 0))    // origin
            (Pnt(10, 0, 0))   // point on X-axis
            (Pnt(0, 10, 0))   // point on Y-axis side

Rectangles

open Euclid

// 2D rectangle from origin, X-direction vector, and Y-size
let rect = Rect2D.createFromXVectorAndWidth(Pt(0, 0), Vc(10, 0), 5.0)

// From direction and sizes
let dir = UnitVc.rotate 45.0 UnitVc.Xaxis
let rotRect = Rect2D.createFromDirectionAndSizes(Pt(0, 0), dir, 10.0, 5.0)

// Rectangle properties
let area = rect.Area
let cx = rect.SizeX
let cy = rect.SizeY
let center = rect.Center
let c0 = rect.Origin                      // origin corner
let c2 = rect.FarCorner                   // diagonally opposite corner

// Point evaluation (u, v parameters from 0 to 1)
let midPoint = rect.EvaluateAt(0.5, 0.5)  // center point

Bounding Boxes

open Euclid

// 3D axis-aligned bounding box from points
let points = [Pnt(0, 0, 0); Pnt(10, 5, 3); Pnt(-2, 8, 1)]
let bbox = BBox.createFromSeq points

// BBox properties
let size = bbox.SizeX, bbox.SizeY, bbox.SizeZ   // 12.0, 8.0, 3.0
let minPt = bbox.MinPnt                           // Pnt(-2, 0, 0)
let maxPt = bbox.MaxPnt                           // Pnt(10, 8, 3)
let center = bbox.Center                          // Pnt(4, 4, 1.5)
let vol = bbox.Volume                             // 288.0

// Create from center and sizes
let box2 = BBox.createFromCenter(Pnt(0, 0, 0), 10.0, 10.0, 10.0)

// Expand bounding box
let bigger = bbox.Expand 1.0               // expand by 1.0 on all sides

// Check containment
let inside = bbox.ContainsPnt(Pnt(5, 4, 2))  // true

// Union of two bounding boxes
let combined = bbox.Union box2

Polylines

open Euclid

// Create a 2D polyline
let pl2d = Polyline2D.createFromPts [Pt(0, 0); Pt(10, 0); Pt(10, 5); Pt(0, 5)]

// Polyline properties
let len = pl2d.Length                      // total length
let count = pl2d.PointCount                // 4
let segs = pl2d.SegmentCount               // 3

// Access segments as Line2D
let firstSeg = pl2d.FirstSegment           // Line2D(Pt(0,0), Pt(10,0))
let seg = pl2d.GetSegment(1)               // Line2D(Pt(10,0), Pt(10,5))

// Modify (polylines are mutable)
pl2d.SetPt (2, Pt(12, 5))                 // move a vertex
let copy = pl2d.Duplicate()                // deep copy

// 3D polyline
let pl3d = Polyline3D.createFromPts [Pnt(0,0,0); Pnt(10,0,0); Pnt(10,5,3)]
let totalLen = pl3d.Length

Transformations with Matrices

open Euclid

let pt = Pnt(5.0, 0.0, 0.0)

// Translation
let m1 = Matrix.createTranslation(10.0, 0.0, 0.0)

// Rotation (angle in degrees)
let m2 = Matrix.createRotationZ 90.0             // 90 degrees around Z
let m3 = Matrix.createRotationX 45.0             // 45 degrees around X

// Rotation around arbitrary axis
let m4 = Matrix.createRotationAxis(UnitVec.Zaxis, 30.0)

// Rotation around axis at a specific center point
let m5 = Matrix.createRotationAxisCenter(Vec(0, 0, 1), Pnt(5, 5, 0), 45.0)

// Scale
let m6 = Matrix.createScale(2.0, 2.0, 1.0)      // scale X and Y by 2

// Shear
let m7 = Matrix.createShear(0.5, 0, 0, 0, 0, 0) // shear XY by 0.5

// Combine transformations with *** operator
let combined = m1 *** m2 *** m6

// Apply to points and vectors
let pt2 = pt.Transform combined                  // full transform (rotate, scale, translate)
let rigid = RigidMatrix.createRotationZ 90.0
let v = Vec(1, 0, 0)
let v2 = v.TransformRigid rigid                  // rotate without translation

// Functional pipeline style
let result =
    pt
    |> Pnt.translate (Vec(10, 0, 0))
    |> Pnt.rotateOnZDeg 90.0
    |> Pnt.scale 2.0
    |> Pnt.transform combined

// Matrix from plane to plane
let fromPlane = PPlane.createThreePoints (Pnt(0,0,0)) (Pnt(1,0,0)) (Pnt(0,1,0))
let toPlane   = PPlane.createThreePoints (Pnt(5,5,0)) (Pnt(6,5,0)) (Pnt(5,6,0))
let planeXform = Matrix.createPlaneToPlane(fromPlane, toPlane)

// Mirror about a plane
let mirror = Matrix.createMirror fromPlane

Quaternion Rotations

open Euclid

// Create quaternion from axis and angle
let q1 = Quaternion.createFromDegree(Vec(0, 0, 1), 90.0)    // 90 degrees around Z
let q2 = Quaternion.createFromRadians(UnitVec.Xaxis, 1.5708) // Pi/2 around X

// Rotate from one direction to another
let q3 = Quaternion.createVecToVec(UnitVec.Xaxis, UnitVec.Yaxis) // X -> Y

// Quaternion properties
let angle = q1.AngleInDegrees                    // 90.0
let axis = q1.Axis                               // Vec close to (0, 0, 1)
let inv = q1.Inverse                             // reverse rotation

// Compose rotations by multiplication
let q4 = q1 * q2                                 // first q2, then q1

// Apply to points and vectors
let rotated = Pnt.rotate q1 (Pnt(1, 0, 0))   // Pnt(0, 1, 0)

// Convert quaternion to matrix
let mat = Matrix.createFromQuaternion q1

Point Cloud Operations

open Euclid

let cloud = ResizeArray [Pnt(0,0,0); Pnt(1,0,0); Pnt(5,5,5); Pnt(10,10,10)]

// Find closest point to a test point
let nearest = Points3D.closestPoint(cloud, Pnt(1.1, 0.1, 0.0))  // Pnt(1,0,0)
let idx = Points3D.closestPointIdx(cloud, Pnt(1.1, 0.1, 0.0))   // 1

// Closest points between two sets
let setA = ResizeArray [Pnt(0,0,0); Pnt(10,10,10)]
let setB = ResizeArray [Pnt(1,0,0); Pnt(20,20,20)]
let (iA, iB) = Points3D.closestPointsIdx(setA, setB)            // (0, 0)

// Most distant point
let farthest = Points3D.mostDistantPoint(cloud, setB)

// center of points
let center = Points3D.center cloud                // Pnt(4, 3.75, 3.75)

Error Handling

Euclid uses descriptive custom exceptions instead of silent failures:

open Euclid

// Unitizing a zero vector raises EuclidException
try
    let bad = Vec(0, 0, 0).Unitized      // fails: can't unitize zero vector
with  e ->
    printfn $"Caught: {e.Message}"

// Division by near-zero raises EuclidDivByZeroException
// NaN or Infinity in inputs raises EuclidException

// Tolerances are conservative for precision engineering:
// - zeroLengthTolerance = 1e-12 (for divisions, unitizing)
// - isTooSmall = 1e-6 (general smallness check)
Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Framework net472 is compatible.  net48 was computed.  net481 was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Euclid:

Package Downloads
Euclid.Rhino

Utilities to use types from Euclid in Rhino3d

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.50.0 51 7/16/2026
0.42.0 202 6/20/2026
0.41.0 180 6/17/2026
0.40.0 164 6/13/2026
0.30.1 157 5/23/2026
0.30.0 136 5/23/2026
0.20.0 505 2/8/2026
0.20.0-beta3 157 1/24/2026
0.20.0-beta2 541 1/23/2026
0.20.0-beta1 124 1/17/2026
0.16.0 705 6/30/2025
0.15.0 318 6/14/2025
0.14.0 709 5/24/2025
0.13.0 365 3/15/2025
0.12.0 548 11/27/2024
0.11.1 573 10/1/2024
0.11.0 224 9/25/2024
0.10.0 239 9/19/2024
0.9.0 762 7/6/2024
0.8.0 458 5/2/2024
Loading failed

### Added

- Axis-aligned bounds are now first-class conversion and intersection types:
- `Box` and `FreeBox` edges can be addressed by endpoint names (`Edge01`, `Edge12`, ... `Edge37`), with matching static members. The old numeric `Edge0`-`Edge11` instance members remain as obsolete aliases.
- `Rect3D.ClosestParameters` and `ClosestPoint`, plus static counterparts.
- `Line2D` and `Line3D` now expose `Start`/`End` aliases and static `setFrom`/`setTo` helpers. `Line2D.intersectRays` returns the unchecked intersection of two non-parallel infinite rays.
- `Tria3D.intersectRay`, `intersectRayXYZ`, and the allocation-free `intersectTriaVectorsWithRay` return the intersection parameter of an infinite line with a triangle.
- Curried `setX`/`setY`/`setZ` helpers for points and vectors, and optimized `ResizeArr.getIdx`/`setIdx` helpers for Fable.
- Missing 2D/3D conversion members: `Vec.AsPt`, `UnitVec.AsPt`, `UnitVec.AsUnitVc` and `Pnt.AsVc`.
- `Points2D.center`, mirroring `Points3D.center`.
- `Rect2D` instance members `Move`, `MoveX`, `MoveY`, `Rotate` and `RotateWithCenter`, plus static `moveX` and `moveY`, mirroring `Rect3D`.
- `Line3D.isCoincidentAndOpposingToFast` static, mirroring `Line2D`.
- `BRect.Edges` and `BRect.edges` returning all 4 edges, mirroring `BBox.Edges`.
- Added `PPlane.asFSharpCode`, `FreeBox.edges`, `RigidMatrix.isIdentity`, and `Rect3D.pointsXYZLooped` static counterparts.

### Changed

- Intersection APIs now consistently use `voption` where absence is expected. This includes the `XLine2D`/`XLine3D` `try*` APIs, line extension intersections, plane intersections, `Box.IntersectRay`, `Rect3D.intersectRay`/`intersectLine`, and `Tria3D.intersectLine`.
- `XLine2D` and `XLine3D` were reorganized: raw coordinate implementations live in the `XLineXY` and `XLineXYZ` modules, while the public types provide point/vector and `Line2D`/`Line3D` overloads. `XLine2D.closestParameters` now returns a struct tuple.
- Plane naming and intersection results were harmonized:
- Bounds conversions now live on the target type: use `BBox.createFromBox`/`createFromRect3D` and `BRect.createFromRect2D`/`createFromRect3D`. The old source-type members remain only as obsolete tuple-returning compatibility helpers.
- `RigidMatrix.Matrix` was renamed to `ToMatrix`. `Quaternion.createFromDegree` was renamed to `createFromDegrees`; the singular form remains obsolete.
- Corrected the public spelling `Colinear` to `Collinear`, including `Polyline2D.removeDuplicateAndCollinearPoints`, `Polyline3D.removeDuplicateAndCollinearPoints`, and offset internals.
- `Line3D.createFromLine2DwithZ` was renamed to `createFromLine2DWithZ`.
- Standardized invalid-point checks and squared-distance naming with `Pt.IsInvalid`/`Pt.isInvalid`, `Pnt.IsInvalid`/`Pnt.isInvalid`, and `Pnt.SqDistanceInXYFromOrigin`/`Pnt.sqDistanceInXYFromOrigin`. The previous misspelled or inconsistent names remain as obsolete aliases.
- Corrected the public spelling `Matrix.createFromColumMajorArray` to `createFromColumnMajorArray`; the misspelled name remains as an obsolete alias.
- `BBox.Union(Pnt)` is now `BBox.UnionPnt` and static `BBox.unionPt` is now `unionPnt`, matching `BRect.UnionPt`/`unionPt`; the old names remain as obsolete aliases.
- `Line2D` and `Line3D` squared point distances now use To-naming: `SqDistanceToPt`/`SqDistanceToXY` and `SqDistanceToPnt`/`SqDistanceToXYZ` replace the `SqDistanceFrom*` forms, which remain as obsolete aliases.
- `Rect2D.PointsXYLoopedCCW`/`PointsXYLoopedCW` are now `PointsXYLooped_CCW`/`PointsXYLooped_CW` (with matching statics), consistent with `PointsXY_CCW`; the old names remain as obsolete aliases.
- Removed duplicate static members that were defined on both the `Line2D`/`Line3D` types and their extension modules; the extension-module versions remain. `Line2D.Direction`, `Tangent` and `UnitTangent` are now extension members, like on `Line3D`.
- Unified the spelling of the optional `varDistParallelBehavior` parameter of `Polyline3D` offsets to match `Polyline2D`.
- Renamed the misleading `Rect3D.PointsXY`/`pointsXY` and `PointsLoopedXY` coordinate-buffer APIs to `PointsXYZ`/`pointsXYZ` and `PointsXYZLooped`/`pointsXYZLooped`; the old names remain as obsolete aliases.
- `Format.rarr` and `Format.iList` are now internal implementation helpers.
- Basic `Line3D` static helpers are declared in `AutoOpenLine3D`, consistently with `AutoOpenLine2D`.

### Fixed

- Reworked 2D and 3D line relationships so intersection, closest-point, closest-parameter, overlap, and squared-distance results are invariant under line order and direction. This also fixes near-parallel, anti-parallel, perpendicular, overlapping, and zero-length cases.
- Corrected the 3D closest-line Lagrange numerators for oblique coplanar and skew lines.
- `Tria3D.intersectLine` now uses a two-sided Moller-Trumbore test, is independent of triangle vertex order, and correctly distinguishes a finite segment from its infinite ray.
- `Polyline2D` and `Polyline3D` offsetting now handles straight runs and intermediate collinear points without losing points or changing round-trip geometry.
- Rectangle and box ray intersections now handle rays on faces or edges, corner grazing, rays starting inside, parallel slabs, and zero-length rays consistently.
- `Line2D.lengthToPtOnLine` and `Line3D.lengthToPtOnLine` now return the signed projected distance from the line start, including points before or beyond the segment.
- Audited and corrected the generated API documentation: counterpart summaries consistently describe their shared behavior without requiring explicit “Same as ...” wording, value-option results use `ValueSome`/`ValueNone`, orientation wording consistently uses “counter-clockwise”, and stale type names, parameter names, grammar, and spelling were corrected.
- Corrected copy-paste doc comments: `Rect3D.Yaxis` and `Rect3D.xAxis` described the wrong axis; `Rect3D.isValid`/`hasArea` statics now cross-reference each other; removed doubled words in `Polyline2D.mapiPt`/`iteriPt` docs.
- Corrected and completed API documentation for matrix fields, record fields, containment behavior, box faces, scaling, axes, coordinate buffers, and assorted spelling and grammar issues.

### Removed

- The obsolete `Intersect` module and its `lineTriangle`/`lineCone` helpers.
- Obsolete `Vc.intersection` and `UnitVc.intersection` helpers, redundant `NPlane.distToPt*` aliases, obsolete infinite `Rect3D` intersection aliases, and static `toString` helpers on bounds/box/rectangle types.