# @expofp/geometry

> ExpoFP SDK internal: shared geometry primitives

Latest version **3.45.8** (published 2026-10-20) · MIT license · 0 weekly downloads

## Install

```sh
npm install @expofp/geometry
pnpm add @expofp/geometry
yarn add @expofp/geometry
bun add @expofp/geometry
```

## Health

**Score 55/100 (C)** — status: active.

Positive: no vulnerabilities; recently updated; high maintenance score.

Warnings: low downloads; no types; no esm support.

## Facts

| | |
|---|---|
| Version | 3.45.8 |
| Published | 2026-10-20 |
| First published | 2026-06-23 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | none |
| Module format | CommonJS |
| Dependencies | 0 |
| Known vulnerabilities | 0 |
| Install scripts | no |
| Maintainers | alexsh_expofp, natalliakaranevichexpofp, daniel.blinder, timur_expofp, rodionnikolaev, korchemkin, 1evgenyt |

## Links

- npm: https://www.npmjs.com/package/@expofp/geometry
- Repository: https://github.com/expofp/efp-app
- Homepage: https://developer.expofp.com/
- npm.io page: https://npm.io/package/@expofp/geometry

## Recent versions

- 3.45.8 (latest) — 2026-10-20
- 3.44.5 (stable) — 2026-10-08
- 3.45.7 — 2026-10-19
- 3.45.6 — 2026-10-16
- 3.45.5 — 2026-10-15
- 3.45.4 — 2026-10-15
- 3.45.3 — 2026-10-14
- 3.45.2 — 2026-10-13
- 3.45.1 — 2026-10-13
- 3.45.0 — 2026-10-13
- 3.44.8 — 2026-10-12
- 3.44.7 — 2026-10-12
- 3.44.6 — 2026-10-09
- 3.44.4 — 2026-10-08
- 3.44.3 — 2026-10-06
- … 94 more at https://npm.io/package/@expofp/geometry/versions

## README

# @expofp/geometry

Zero-dependency **2.5D** geometry primitives for the ExpoFP SDK: `Point`, `Line`, `Box`, `Rect`,
`Polygon`, `Mesh`, plus the free functions that operate on them. No runtime dependencies (not even
`three`).

**"2.5D"** means: a 3D `Point` vector, but the **shapes are 2D geometry positioned in 3D by an
`elevation`** — a flat shape at a given height. All spatial math (distance, angle, area, containment,
intersection) is computed in the **xy plane**; `elevation` positions the shape and gates intersections
(see Coordinate convention). The one genuinely-3D shape is `Mesh` (a triangle mesh whose vertices carry
real `z`), for renderer geometry that isn't flat.

## Primitives

| Type      | What it is                                                                                                                                                                                                                                                                                                     |
| --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Point`   | A mutable 3D vector (`x`, `y`, `z`). `width`/`height` alias `x`/`y` so a point can double as a size. The low-level building block the shapes write into.                                                                                                                                                       |
| `Line`    | A segment between two **2D** points (`p0`, `p1`) at a single `elevation` (default 0).                                                                                                                                                                                                                          |
| `Box`     | An **axis-aligned** rectangle (min/max corners, cached center/size). 2D, **infinite in z** (no elevation) — for bounding boxes and screen sizes. A `Box` structurally satisfies `RectLike`, so anything taking a `RectLike` accepts a `Box` (an unrotated, elevation-less rect that intersects at any height). |
| `Rect`    | A rectangle that **may be rotated**: center + size + `rotation` + `elevation` (both default 0). For oriented rectangular shapes at a given height.                                                                                                                                                             |
| `Polygon` | A **planar** triangulated polygon: 2D `vertices` + triangle `indices` + an `elevation`, with a cached bounding `Box`. Has `containsPoint`/`area` (well-defined because it's flat). Build a convex one from an ordered ring with `Polygon.fromConvexRing`.                                                      |
| `Mesh`    | A **3D** triangle mesh: 3D `vertices` + `indices`, with a cached (xy) bounding `Box`. Transforms only — **no `containsPoint`/`area`** (undefined for a non-planar mesh). For renderer geometry that isn't flat.                                                                                                |

`Shape = Box | Rect | Polygon | Mesh`, discriminated by `is*` brand fields and narrowed with the exported
guards `isBox` / `isRect` / `isPolygon` / `isMesh`.

**Coordinate convention:** `x` increases left→right, `y` increases top→bottom (screen, y-down). Rotation
is in **radians, positive = clockwise**, uniform across every primitive and helper (`Rect`, `Polygon`,
`Mesh`, and the `point*`/`line*` angle functions). `elevation` (and `Point.z`) is the out-of-plane height;
positive is toward the viewer. **Spatial operations are computed in the xy plane** — distances, angles,
areas, and `containsPoint` ignore `z`/`elevation`. **Intersection** tests (`lineIntersection`,
`intersectLineRect`) compare `elevation` only when **both** operands define it (an `undefined` elevation —
a `Box`, or a bare `LineLike` — means "any height" and always intersects); when both are defined they
intersect only within an `elevationTolerance` (default `1e-3`), and the result point's `z` is the shared
elevation.

## Design decisions

### 2.5D: 2D shapes + elevation, one 3D `Mesh`

`Point` is the only inherently-3D type (a vector). Every _shape_ is 2D positioned by `elevation` (`Box`
has none — it is infinite in z). `Polygon` is planar, so its `containsPoint`/`area` are exact; genuinely
3D geometry uses `Mesh`, which deliberately omits those operations (a "contains point" on a non-planar
mesh would only be an xy-silhouette test).

### Immutable by default, with `set` mutators

Shapes are immutable value objects: getters return readonly views and every transform returns a **new**
instance, so shared geometry (e.g. a booth's rect) can't be mutated out from under another reader. Each
primitive (`Point`, `Line`, `Box`, `Rect`, `Polygon`, `Mesh`) exposes a single public, docs-flagged
`set(...)` mutator — the escape hatch the transforms write through, and the way to update fields like
`elevation` in place.

### Opt-in mutation via a `target` parameter

Because immutable transforms allocate, every transform also accepts an optional **`target`** to write the
result into instead of allocating — the same idiom three.js uses, but inverted so the default is pure:

```ts
const moved = box.translate(offset); // new Box (immutable default)
box.translate(offset, box); // writes into box itself (opt-in mutation)
box.translate(offset, scratch); // reuse a scratch Box in a hot loop
```

Free functions read all inputs before writing the target, so passing the input as the target
(`f(x, …, x)`) is safe. Use `target` only where profiling shows allocation matters.

### Classes + free functions (the duality)

Every operation is a **pure free function** over structural `*Like` inputs, and each class exposes a
**thin method** that forwards to it:

```ts
lineLength(line); // free function, accepts any { p0, p1 }
line.length(); // method → lineLength(this)

boxTranslate(box, offset); // free function
box.translate(offset); // method → boxTranslate(this, offset)
```

Free functions accept structural shapes (`Point2Like`, `LineLike`, `RectLike`, `MeshLike`, …), so callers
don't need class instances and there is no class-identity coupling. Methods give discoverability and
chaining.

### Transform/merge logic defined once (`Polygon` reuses `Mesh`)

`Polygon` structurally satisfies `MeshLike` (its 2D vertices satisfy `PointLike`), so the transform and
merge logic lives **once** on the mesh free functions — `meshTranslate` / `meshScale` / `meshRotate` /
`meshMerge` (plus `meshBounds`). Both `Mesh`'s and `Polygon`'s methods forward to them via the optional
`target`: a `Polygon` target keeps the result a `Polygon` (elevation preserved), a `Mesh` target keeps it
a `Mesh`.

### Structural input types

Inputs are the loose `*Like` types (`Point2Like = { x, y }`, `RectLike = { center, size, rotation?,
elevation? }`, `MeshLike`, `PolygonLike`, …); outputs are concrete class instances. Ops like `translate` /
`expand` / `scale` take `number | Point2Like` (planar shapes such as `Box`) or `number | PointLike` (the
3D `Point` ops) — a scalar broadcasts to both axes, a point gives per-axis values.

### Brands instead of `instanceof`

Each primitive carries a `readonly is<Name> = true` field (three.js style) so type discrimination
survives duplicate module copies and serialization. The `Shape` guards check the brand rather than
`instanceof`, and accept only a `Shape` (a stray `{ isBox: true }` object cannot masquerade as a box).

## Geographic (`geo`)

The `geo` module provides pure geodetic math on a WGS-84 sphere. It is a **separate
coordinate space** from the planar primitives above — do not mix them without an
explicit bridge.

| Symbol                                          | Description                                                                             |
| ----------------------------------------------- | --------------------------------------------------------------------------------------- |
| `LatLng`                                        | `{ lat: number; lng: number }` — decimal degrees; lat north-positive, lng east-positive |
| `GeoAnchor`                                     | `{ local: Point2Like; geo: LatLng }` — a correspondence used by the projection bridge   |
| `haversineDistance(a, b)`                       | Great-circle distance in metres                                                         |
| `bearing(from, to)`                             | Initial compass bearing in degrees `[0, 360)`                                           |
| `destinationPoint(from, distanceM, bearingDeg)` | Destination `LatLng` given origin, distance, and bearing                                |
| `projectLocalToGps(point, a, b)`                | Maps a local `Point2Like` to `LatLng` via two anchors                                   |
| `projectGpsToLocal(geo, a, b)`                  | Maps a `LatLng` to a local `Point2Like` via two anchors — inverse of above              |

**Convention (different from the planar primitives):**

- Coordinates are in **degrees** (not radians).
- Bearings are **compass**: **0 = North, 90 = East, 180 = South, 270 = West**, increasing clockwise.
- Earth radius constant: **6 371 000 m** (WGS-84 mean radius).

This is deliberately firewalled from the planar convention (angles in radians,
positive-clockwise starting from the +x axis, y-down screen coordinates,
Euclidean distance). The only bridge is `projectLocalToGps` / `projectGpsToLocal`,
which takes validated `GeoAnchor` inputs — no floorplan-domain logic enters `geo`.

---
_Source: https://npm.io/package/@expofp/geometry · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
