use-geometry 0.0.4

Utility-first 2D geometry primitives for RustUse
Documentation

use-geometry

use-geometry provides small, composable geometry building blocks rather than a large geometry engine. The crate keeps the model explicit: direct structs, standalone helpers, validated constructors for external input, and tolerance-aware APIs only where ambiguity needs to be made explicit.

What this crate provides

Area Root exports Best fit
Coordinates and vectors Point2, Vector2 Direct 2D value math
Distance and midpoint distance_2d, distance_squared_2d, midpoint_2d Common point-to-point calculations
Orientation Orientation2, orientation_2d, try_orientation_2d, tolerance-aware variants Winding and collinearity checks
Lines and segments Line2, Segment2 Directed 2D relationships and interpolation
Shapes and bounds Circle, Triangle, Aabb2, aabb_from_points Shape metrics, containment, and bounds checks
If you need to... Start here
Validate coordinates and shapes from external input try_new constructors
Work with trusted values in hot paths Infallible constructors like Point2::new(...)
Build simple containment or broad-phase checks Aabb2
Choose exact vs approximate geometric tests explicitly Tolerance-aware helpers

When to use it directly

Choose use-geometry directly when geometry is the only math surface your application needs, or when you want to avoid pulling a broader facade into the dependency graph.

Scenario Use use-geometry directly? Why
You only need 2D primitives and measurements Yes The crate stays narrow and explicit
You want validated construction at the boundary of your app Yes try_new and tolerance helpers make failures explicit
You also need checked combinatorics Usually no use-math may be the better integration point
You need a broad geometry engine with intersections and polygons No This crate intentionally stops short of that scope

Installation

[dependencies]
use-geometry = "0.0.1"

Quick examples

Validated construction from external input

use use_geometry::{
    Aabb2, Circle, Orientation2, Point2, Segment2, Triangle, midpoint_2d, try_orientation_2d,
};

let a = Point2::try_new(0.0, 0.0)?;
let b = Point2::try_new(4.0, 0.0)?;
let c = Point2::try_new(0.0, 3.0)?;

let segment = Segment2::try_new(a, b)?;
let triangle = Triangle::try_new(a, b, c)?;
let circle = Circle::try_new(a, 3.0)?;
let bounds = Aabb2::from_points(a, c);

assert_eq!(segment.midpoint(), midpoint_2d(a, b));
assert_eq!(triangle.area(), 6.0);
assert_eq!(triangle.perimeter(), 12.0);
assert_eq!(try_orientation_2d(a, b, c)?, Orientation2::CounterClockwise);
assert_eq!(circle.center(), a);
assert_eq!(circle.radius(), 3.0);
assert!(bounds.contains_point(Point2::new(0.0, 1.5)));
# Ok::<(), use_geometry::GeometryError>(())

Bounds and tolerance-aware orientation

use use_geometry::{Aabb2, Orientation2, Point2, orientation_2d_with_tolerance};

let bounds = Aabb2::from_points(Point2::new(4.0, 1.0), Point2::new(1.0, 3.0));

assert_eq!(bounds.min(), Point2::new(1.0, 1.0));
assert_eq!(bounds.max(), Point2::new(4.0, 3.0));
assert_eq!(bounds.center(), Point2::new(2.5, 2.0));
assert!(bounds.contains_point(Point2::new(2.0, 2.0)));
assert_eq!(
    orientation_2d_with_tolerance(
        Point2::new(0.0, 0.0),
        Point2::new(1.0, 1.0),
        Point2::new(2.0, 2.0 + 1.0e-12),
        1.0e-11,
    )?,
    Orientation2::Collinear,
);
# Ok::<(), use_geometry::GeometryError>(())

Shape metrics stay direct

use use_geometry::{Orientation2, Point2, Triangle, triangle_area, triangle_twice_signed_area};

let a = Point2::try_new(0.0, 0.0)?;
let b = Point2::try_new(4.0, 0.0)?;
let c = Point2::try_new(0.0, 3.0)?;
let triangle = Triangle::try_new(a, b, c)?;

assert_eq!(triangle.orientation(), Orientation2::CounterClockwise);
assert_eq!(triangle.twice_signed_area(), triangle_twice_signed_area(a, b, c));
assert_eq!(triangle.area(), triangle_area(a, b, c));
assert_eq!(triangle.sides(), [4.0, 5.0, 3.0]);
assert_eq!(triangle.perimeter(), 12.0);
assert_eq!(triangle.centroid(), Point2::new(4.0 / 3.0, 1.0));
assert!(!triangle.is_degenerate());
# Ok::<(), use_geometry::GeometryError>(())

Degeneracy stays explicit

[!IMPORTANT] Exact degeneracy and tolerance-based degeneracy are different checks. In use-geometry, callers should choose deliberately based on coordinate scale, input quality, and whether they care about mathematical collapse or practical numeric collapse.

Degeneracy occurs when a geometric value collapses into a lower-dimensional or invalid form. For triangles, exact degeneracy means the vertices are collinear and the signed twice-area is exactly zero.

f64::EPSILON is usually not a useful geometry tolerance. It is tied to floating-point precision around 1.0, not the coordinate scale of a triangle. Practical degeneracy checks should use a caller-provided tolerance chosen for the coordinate scale, input source, and expected numeric noise.

use use_geometry::{Point2, Triangle};

let triangle = Triangle::try_new(
  Point2::try_new(0.0, 0.0)?,
  Point2::try_new(4.0, 0.0)?,
  Point2::try_new(8.0, 1.0e-13)?,
)?;

assert!(!triangle.is_degenerate());
assert!(triangle.is_degenerate_with_tolerance(1.0e-12)?);
# Ok::<(), use_geometry::GeometryError>(())

Validation model

Use try_new when values may come from user input, files, network payloads, or other untrusted sources. Use infallible constructors like Point2::new(...), Vector2::new(...), Triangle::new(...), or Aabb2::from_points(...) when values are already trusted and you want direct assembly.

[!IMPORTANT] Tolerance-aware helpers reject negative and non-finite tolerances explicitly. This crate does not hide geometric ambiguity behind a global epsilon policy.

Scope

  • Small APIs are preferred over broad trait-heavy abstractions.
  • Primitives are intended to compose cleanly with each other and with plain f64 values.
  • Built-in approximate equality policies and global epsilon choices are intentionally out of scope.
  • Polygon, intersection, and spatial indexing APIs are intentionally deferred.
  • Plane-oriented APIs are deferred until a later 3D geometry layer.

Status

use-geometry is a concrete pre-1.0 crate in the RustUse docs surface. The API remains intentionally small, and the RustUse-hosted generated rustdocs stay canonical while external crates.io and docs.rs pages remain staged.