Skip to content

geom.polylineFrame ​

Arc-length frame along a plan polyline. Every "distance along a wall / edge / road" question in the pack modules goes through this so a curve needs no special case anywhere downstream. Plan coordinates are x/z in the caller's length unit, Y is up and never appears here.

The second half of the file offsets that frame. One frame, one band: a centreline becomes an arc-length frame with a mitred offset direction at every vertex, so a boundary at arc position d is a pure function of d — two neighbouring cells share the identical wall by construction and a gap between them is not representable. Offsetting the frame to t0/t1 and walking it from d0 to d1 gives the band polygon for any arc span, mitred through the vertices it crosses.

An envelope is honoured in the arc domain, not by polygon clipping: ask how deep the band may go at an arc position ({@link maxInsideOffset}) and which arc spans are buildable at all ({@link contiguousSpans}). Both are sampled and then bisected to the boundary, which is exact enough for plan work and cannot produce the self-intersecting rings a general clipper does. For real polygon booleans see geom/polygon-boolean.ts.

Units: every length is in whatever unit the caller's coordinates are in; the algorithms compute natively on the caller's numbers. The module's own tolerances are defined in millimetres and scaled to the optional trailing unit parameter (default "mm") with constMm, so they keep one physical meaning in every unit. Pass the unit your coordinates are in.

Pure module: no host, no DOM, no clock, no RNG. Inputs and outputs are plain data (structuredClone-safe).

ts
// plugin worker
import { polylineFrame, straightFrame, isBentPolyline, frameAlong, projectOntoPolyline, pointInEnvelope } from "@snaptrude/plugin-toolkit/geom-polyline-frame";

// agent run_worker
modules.geom.polylineFrame.polylineFrame(...)
modules.geom.polylineFrame.straightFrame(...)
modules.geom.polylineFrame.isBentPolyline(...)

Every input carries unit ("mm" | "cm" | "m" | "ft" | "in"). All lengths in that call — input and output — are in it; angles are degrees, areas are that unit squared.

When to use it ​

Use it forNot for
Turning a distance along a wall, edge or road into a world point, tangent and normalPolygon booleans and ring offsets — see geom.polygonBoolean
Offsetting a centreline into mitred bands, so two neighbouring cells share the identical wallClosed-ring primitives — see geom.polygon
Asking how deep a band may reach inside an envelope, and which arc spans are buildable at allDoubly-curved or three-dimensional paths

Inputs and outputs ​

The shapes every function below speaks. Each appears once; a field's note says what changing it does to the result.

PlanPoint ​

A point in plan, in the caller's length unit.

  • x: number
  • z: number

PolylineFrame ​

A polyline with its cumulative arc length precomputed. Build one with polylineFrame; treat it as read-only afterwards.

  • pts: PlanPoint[] — The sampled points. A straight edge has two; an arc has as many as it needs.
  • cum: number[] — Arc length from pts[0] to each point. cum[0] === 0.
  • length: number — Total arc length, cum[cum.length - 1].
  • curved: boolean — True when the polyline runs measurably longer than its chord (see isBentPolyline).

PlanVector ​

A unit-ish direction in plan. Never carries units.

  • x: number
  • z: number

FrameSample ​

Position and local axes at a distance along the polyline.

  • x: number
  • z: number
  • tangent: PlanVector — Unit tangent, pointing in the direction of increasing d.
  • normal: PlanVector — Unit normal. Points to the outward side when one was given, else to the tangent's right ({z, -x}).

OffsetFrame ​

A polyline ready to be offset: the arc-length frame, the unit direction of each leg, the offset direction at each vertex (mitred, capped) and the signed turn at each vertex.

  • frame: PolylineFrame
  • dirs: PlanVector[] — One per leg, pts.length - 1 entries.
  • offsets: PlanVector[] — One per point. Ends carry their leg's left normal; interior vertices carry the mitre direction, scaled so an offset of t reaches the mitre point. On a closed ring both ends carry the seam's mitre, which is the same direction at both, so the band closes on itself without a notch.
  • turnDeg: number[] — Signed turn at each point in degrees, 0 at both ends — except on a closed ring, where both ends carry the seam vertex's own turn. Positive means the path turns towards its left normal, so the left side is the concave one.
  • closed: boolean — True when the last point returns to the first: the seam is a vertex like any other, and d = 0 and d = length are the same place on the ground.
  • envelope?: Envelope — When given, a mitre point that would fall outside it is pulled back along the mitre until it is inside, so no band polygon leaves the envelope at a bend even though its perpendicular offsets are all inside.

Span ​

An arc-length interval along a frame.

  • d0: number
  • d1: number

Functions ​

polylineFrame(ptsIn, dedupeTol, unit) ​

Build a frame from plan points. Consecutive duplicates (within dedupeTol, default 0.01 mm scaled to unit) are dropped so zero-length pieces never appear in the arc length.

Tolerances are defined in mm and scaled to unit; pass the unit your coordinates are in.

  • Parameters:
    • ptsIn: PlanPoint[]
    • dedupeTol?: number
    • unit?: LengthUnit — Default "mm".
  • Returns: PolylineFrame
  • Throws: when fewer than two distinct points remain.

straightFrame(start, direction, length, unit) ​

A straight frame from a start point, a direction and a length. Tolerances are defined in mm and scaled to unit; pass the unit your coordinates are in.


isBentPolyline(pts) ​

A polyline is curved when it runs measurably longer (0.2 %) than the straight line between its ends. Asks the points, not the source geometry, so it cannot disagree with the arc length used everywhere else.


frameAlong(frame, d, outward) ​

Position, tangent and normal at arc length d. d is clamped to the polyline. When outward is given the normal is flipped to agree with it, which is how a run's outward side propagates to every sample on a curve.


projectOntoPolyline(frame, p) ​

Arc-length distance along the polyline to the point nearest p, and how far p sits off the polyline. Measured by arc length, not by chord: projecting onto the chord is only right for a straight edge and compresses an arc towards its middle.


pointInEnvelope(p, env) ​

Inside the envelope's ring and outside every hole, by ray parity.

  • Parameters:
  • Returns: boolean

Effect: a ring of fewer than three points is treated as no constraint at all rather than as empty ground, so an envelope may carry holes without an outer ring.


offsetFrame(ptsIn, envelope, unit) ​

Build an offsettable frame from centreline points. Duplicate points are dropped by polylineFrame.

A path whose last point returns to its first is a closed ring: closed is set, and the seam gets the same mitred offset and signed turn as any other vertex, so a band built across it meets itself instead of butting two square ends together.

Tolerances are defined in mm and scaled to unit; pass the unit your coordinates are in.

  • Parameters:
    • ptsIn: PlanPoint[]
    • envelope?: Envelope
    • unit?: LengthUnit — Default "mm".
  • Returns: OffsetFrame
  • Throws: when fewer than two distinct points remain.

offsetPointAt(cf, d, t, unit) ​

The point t to the left of the centreline at arc position d. Negative t is the right side. At a vertex the offset runs along the mitre, which is what makes a wall at a bend shared by the cells on both sides of it.

Tolerances are defined in mm and scaled to unit; pass the unit your coordinates are in.


offsetChain(cf, d0, d1, t, unit) ​

The offset boundary from d0 to d1 at offset t, including every vertex the span crosses.

Tolerances are defined in mm and scaled to unit; pass the unit your coordinates are in.

  • Parameters:
    • cf: OffsetFrame
    • d0: number
    • d1: number
    • t: number
    • unit?: LengthUnit — Default "mm".
  • Returns: PlanPoint[]

bandRing(cf, d0, d1, t0, t1, unit) ​

The band polygon over [d0, d1] between offsets t0 and t1. Both offsets take the same sign: positive is the left side, negative the right.

Tolerances are defined in mm and scaled to unit; pass the unit your coordinates are in.

  • Parameters:
    • cf: OffsetFrame
    • d0: number
    • d1: number
    • t0: number
    • t1: number
    • unit?: LengthUnit — Default "mm".
  • Returns: PlanPoint[]

Effect: the ring is the cell outline a caller draws. Winding follows the sign of the offsets, so read areas through an unsigned area function.


maxInsideOffset(cf, d, t0, t1, env, unit) ​

How far the band may reach at arc position d before it leaves the envelope: t1 when the full depth fits, t0 when even the corridor edge is outside, else the bisected boundary.

Tolerances are defined in mm and scaled to unit; pass the unit your coordinates are in.

  • Parameters:
    • cf: OffsetFrame
    • d: number
    • t0: number
    • t1: number
    • env: Envelope
    • unit?: LengthUnit — Default "mm".
  • Returns: number

contiguousSpans(length, step, test, unit) ​

The arc spans of [0, length] where test holds, longest first. Sampled every step and then bisected to the flip, so a span's ends are the real boundary rather than a sample.

The step floor is defined in mm and scaled to unit; pass the unit your lengths are in.

  • Parameters:
    • length: number
    • step: number
    • test: (d: number) => boolean
    • unit?: LengthUnit — Default "mm".
  • Returns: Span[]

Effect: a feature narrower than step can be missed entirely; pick a step below the smallest span that matters.

Examples ​

A world point, tangent and normal at a distance along a wall (both)

ts
const frame = polylineFrame(wall);
return frameAlong(frame, d);

One cell outline off a corridor centreline, mitred through the bends (both)

ts
const cf = offsetFrame(centreline);
return bandRing(cf, d0, d1, corridorHalfWidth, corridorHalfWidth + cellDepth);

Errors ​

ConditionResult
polylineFrame() — fewer than two distinct points remain.throws
offsetFrame() — fewer than two distinct points remain.throws