Skip to content

@carrot/maths-geometry ​

ts

Vectors, matrices, quaternions, transforms, 2D/3D shapes, curves, paths, and axis-aligned bounds. Built on @carrot/maths for core utilities.

Installation ​

ts
import { Vector2, Matrix4x4, Quaternion, Bezier2 } from '@carrot/maths-geometry';

Architecture ​

Immutable + Mutable Pattern ​

All value types (vectors, matrices, sizes) follow the same pattern:

  • Immutable class (Vector2, Matrix3x2, etc.) - all operations return new instances. Safe for general use.
  • Mutable class (MutableVector2, MutableMatrix3x2, etc.) - in-place operations for hot paths where allocation matters.
  • Like interface (Vector2Like, Matrix3x2Like, etc.) - plain data shape for interop. Both immutable and mutable classes satisfy the interface.

Convert between them: vec.toMutable(), MutableVector2.from(vec), etc.

Transform Hierarchy ​

Transforms support parent/child relationships with dirty-flag world matrix caching:

Transform2D uses a cost-optimised hierarchy - createChild picks the cheapest variant:

  • Transform2DNone - identity, zero overhead
  • Transform2DOffset - translation only, skips rotation/scale
  • Transform2DAffine - full position + scale + rotation

Transform3D uses position (Vector3) + rotation (Quaternion) + scale (Vector3), composing a TRS local matrix that chains through the parent hierarchy.

Geometry2 Interface ​

All 2D shapes implement Geometry2, which extends HasBounds2:

ts
interface Geometry2 {
    readonly bounds: Bounds2;
    contains(point: Vector2Like): boolean;
    closestPoint(point: Vector2Like): Vector2;
    distanceTo(point: Vector2Like): number;  // signed distance (negative = inside)
}

This uniform interface means shapes, paths, and loops can be used interchangeably for hit testing, distance queries, and spatial operations.

Curves and Paths ​

Bezier2 supports linear, quadratic, and cubic curves with exact bounds computation (quadratic root finding, not sampling). Tessellation converts to LineSegment2 arrays for rasterisation.

Path2<T> is a generic path over any segment type. Concrete paths:

  • LineStrip2 / LineLoop2 - polylines (open/closed)
  • BezierStrip2 / BezierLoop2 - bezier paths (open/closed)

Closed paths (LineLoop2, BezierLoop2) implement Geometry2 with ray-casting containment and signed area via the shoelace formula.

File Structure ​

src/
├── index.ts              # Public API barrel
├── interfaces/
│   ├── scalar.ts         # HasLength, HasRadius
│   ├── spatial2.ts       # 2D spatial traits
│   ├── spatial3.ts       # 3D spatial traits
│   ├── geometry2.ts      # Geometry2 interface
│   └── geometry3.ts      # Geometry3 interface
├── vectors/
│   ├── vector2.ts        # Vector2, MutableVector2
│   ├── vector3.ts        # Vector3, MutableVector3
│   ├── vector4.ts        # Vector4, MutableVector4
│   └── vectorAny.ts      # Union type aliases
├── matrices/
│   ├── matrix.ts         # Generic Matrix interface
│   ├── matrix3x2.ts      # 2D affine
│   ├── matrix3x3.ts      # 2D projective
│   └── matrix4x4.ts      # 3D transforms
├── quaternions/
│   └── quaternion.ts     # Quaternion
├── transforms/
│   ├── transform.types.ts
│   ├── transformConfig.ts
│   ├── transform2d.ts    # Abstract + 3 concrete variants
│   └── transform3d.ts    # Full 3D transform
├── sizes/
│   ├── size2.ts          # Size2, MutableSize2
│   ├── size3.ts          # Size3, MutableSize3
│   ├── aspectRatio.ts    # AspectRatio
│   └── resolution.ts     # Resolution alias
├── bounds/
│   ├── bounds2.ts        # Bounds2 (2D AABB)
│   └── bounds3.ts        # Bounds3 (3D AABB)
├── shapes/
│   ├── circle2.ts        # Circle2
│   ├── ellipse2.ts       # Ellipse2
│   ├── rect2.ts          # Rect2
│   └── triangle2.ts      # Triangle2
├── lines/
│   ├── infiniteLine2.ts  # InfiniteLine2
│   ├── infiniteLine3.ts  # InfiniteLine3
│   ├── ray2.ts           # Ray2
│   ├── ray3.ts           # Ray3
│   ├── lineSegment2.ts   # LineSegment2
│   └── lineSegment3.ts   # LineSegment3
├── curves/
│   ├── arc2.ts           # Arc2
│   └── bezier2.ts        # Bezier2
└── paths/
    ├── path2.ts          # Path2<T> generic
    ├── lineStrip2.ts     # LineStrip2
    ├── lineLoop2.ts      # LineLoop2
    ├── bezierStrip2.ts   # BezierStrip2
    └── bezierLoop2.ts    # BezierLoop2

Dependencies ​

DependencyKind
@carrot/mathsruntime

Build ​

bash
npm run build

Compiles TypeScript to ESM via tsc. Output lands in dist/.


Usage Guide ​

Vectors, matrices, transforms, shapes, curves, and paths for 2D/3D applications.

Import ​

ts
import { Vector2, Vector3, Matrix4x4, Quaternion } from '@carrot/maths-geometry';

Common Patterns ​

1. Vectors ​

ts
const a = Vector2.create(3, 4);
const b = Vector2.create(1, 2);

a.add(b);              // Vector2(4, 6)
a.scale(2);            // Vector2(6, 8)
a.normalize();         // unit vector
a.dot(b);              // 11
a.distance(b);         // ~2.83
Vector2.lerp(a, b, 0.5);  // midpoint

3D vectors have directional constants:

ts
Vector3.up;        // (0, 1, 0)
Vector3.forwards;  // (0, 0, -1)

2. Mutable vectors (hot paths) ​

ts
const v = MutableVector2.create(0, 0);
v.addInPlace(delta);
v.scaleInPlace(speed);
v.normalizeInPlace();

3. 2D transforms (Matrix3x2) ​

ts
const m = Matrix3x2.identity
    .translate(100, 200)
    .rotate(Math.PI / 4)
    .scale(2, 2);

const worldPos = m.transformPoint(localPos);

// Decompose back
m.translation;  // Vector2
m.rotation;     // radians
m.scaleVector;  // Vector2

4. 3D transforms (Matrix4x4) ​

ts
const view = Matrix4x4.lookAt(eye, target, Vector3.up);
const proj = Matrix4x4.perspective(fovY, aspect, near, far);
const mvp  = proj.multiply(view).multiply(model);

5. Quaternions ​

ts
const q = Quaternion.fromEuler(pitch, yaw, roll);
const look = Quaternion.lookRotation(direction);

// Smooth rotation
const blended = Quaternion.slerp(from, to, t);

// Apply to vector
const rotated = q.rotateVector(Vector3.forwards);

// Direction getters
q.forward;  // Vector3
q.up;       // Vector3

6. Transform hierarchy ​

ts
const parent = Transform3D.create({ position: Vector3.create(0, 5, 0) });
const child  = Transform3D.create({ position: Vector3.create(1, 0, 0) }, parent);

child.worldPosition;  // (1, 5, 0)
child.worldMatrix;    // cached, recomputed on dirty

2D transforms auto-select the cheapest variant:

ts
const root  = Transform2D.createChild(null, { kind: 'offset', x: 100, y: 200 });
const child = Transform2D.createChild(root, { kind: 'affine', x: 10, rotation: 0.5 });

7. Shapes and hit testing ​

ts
const circle = Circle2.create(Vector2.create(5, 5), 3);
circle.contains(Vector2.create(6, 6));    // true
circle.distanceTo(point);                 // SDF (negative = inside)
circle.closestPoint(point);               // nearest point on perimeter

All shapes implement Geometry2 - uniform interface for contains, closestPoint, distanceTo.

8. Bezier curves ​

ts
const curve = Bezier2.cubic(p0, p1, p2, p3);

curve.solve(0.5);           // point at t=0.5
curve.derivative(0.5);      // tangent vector
curve.bounds;               // exact AABB via root finding

// Tessellate to line segments
const segments = curve.tessellate(20);

// Subdivide (De Casteljau)
const [left, right] = curve.subdivide(0.5);

9. Paths ​

ts
const strip = LineStrip2.fromPoints([p1, p2, p3, p4]);
const loop  = LineLoop2.fromPoints([p1, p2, p3]);  // auto-closes

loop.contains(point);     // ray-casting
loop.signedArea;           // shoelace formula
loop.isClockwise;

// Bezier paths
const bezierPath = BezierStrip2.fromPoints([p1, p2, p3, p4], 'cubic');
const lineApprox = bezierPath.toLineStrip(20);

10. Bounds ​

ts
const box = Bounds2.fromMinMax(Vector2.zero, Vector2.create(10, 10));
box.contains(point);
box.intersects(otherBox);
Bounds2.union(box, otherBox);
Bounds2.enclosing([point1, point2, point3]);

Tips ​

  • Use immutable types by default - switch to mutable only in measured hot paths.
  • Geometry2 is your friend - shapes, closed paths, and rects all implement it, so hit testing and distance queries are uniform.
  • Bezier bounds are exact - computed via quadratic root finding, not sampling. No need to tessellate just for AABB.

Carrot