Euclid 0.50.0
Prefix Reserveddotnet add package Euclid --version 0.50.0
NuGet\Install-Package Euclid -Version 0.50.0
<PackageReference Include="Euclid" Version="0.50.0" />
<PackageVersion Include="Euclid" Version="0.50.0" />
<PackageReference Include="Euclid" />
paket add Euclid --version 0.50.0
#r "nuget: Euclid, 0.50.0"
#:package Euclid@0.50.0
#addin nuget:?package=Euclid&version=0.50.0
#tool nuget:?package=Euclid&version=0.50.0

Euclid
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.
Related Projects
Euclid.Rhino - Rhino3D integration
License
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 | Versions 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. |
-
.NETFramework 4.7.2
- FSharp.Core (>= 6.0.7)
-
net6.0
- FSharp.Core (>= 6.0.7)
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 |
### 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.