Appearance
@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 overheadTransform2DOffset- translation only, skips rotation/scaleTransform2DAffine- 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 # BezierLoop2Dependencies ​
| Dependency | Kind |
|---|---|
@carrot/maths | runtime |
Build ​
bash
npm run buildCompiles 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); // midpoint3D 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; // Vector24. 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; // Vector36. 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 dirty2D 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 perimeterAll 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.
Geometry2is 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.