/** * Declarative georeferencing — `site-origin` / `site-heading` / `site-scale`. * * An IFC export tells you where it thinks it is, and it is very often wrong: * authoring tools ship a default project location (the Medical-Dental Clinic * sample carries Revit's Boston default; the Duplex carries a Chicago city * centre point), `IfcMapConversion` is absent from most IFC2x3 files, and * `TrueNorth` is routinely left unset. Baking that into the tileset at * conversion time means every correction is a reconversion. * * So placement moves to the layer, where it is an attribute like any other — * inspectable in the markup, settable by the IFC loader from whatever it * managed to extract, and undoable/story-steppable for free. * * Two transports, because the two layers that make up an IFC model are * anchored differently: * * - The MESH is a 3D Tiles tileset whose root already carries an * east/north/up frame. deck's `Tile3DLayer` forwards `loadOptions.tileset` * into the `Tileset3D` constructor, and `Tileset3D` PRE-multiplies its * `modelMatrix` onto the root transform, so relocating means cancelling * the baked frame first: `target · Rz · S · baked⁻¹`. That needs the baked * frame, hence `tilesetBakedFrame` below. * - The OUTLINES are a `PathLayer` of local east/north/up metres, which * `METER_OFFSETS` places directly from `coordinateOrigin` — no baked frame * to cancel, and rotation/scale ride a plain `modelMatrix` uniform, so * they are free to change per frame. */ import { Matrix4 } from "@math.gl/core"; export interface SitePlacement { /** Absent when the author gave only a heading/scale — rotate in place. */ origin: [number, number, number] | null; /** Degrees CLOCKWISE from true north, matching how a survey bearing reads. */ heading: number; scale: number; } /** * Reads the three placement props off a resolved layer IR. * * Returns null when none is authored, which is the common case and must stay * free: no prefetch, no matrix, no change to how the layer loads. */ export declare function parseSitePlacement(props: Record): SitePlacement | null; /** * Short, DOT-FREE token for "has the placement changed?" — appended to a * tileset URL to force deck to reload (see the cache-bust in runtime-core). * * Hashed rather than spelled out because loaders.gl types a tile by * `url.split('?')[0].split('.').pop()`, and a coordinate spelled into the * fragment (`#site=-71.059776,…`) hands that sniffer `059776` instead of * `json`. Tilesets that force `isTileset` would survive it; ones relying on * auto-detection would not, and the failure is the silent kind where nothing * renders. Base-36 of a 32-bit FNV-1a has no dots by construction. */ export declare function sitePlacementToken(p: SitePlacement): string; /** * Local east/north/up frame at a geodetic point, as the column-major matrix * 3D Tiles calls `root.transform`. */ export declare function enuMatrix(lonDeg: number, latDeg: number, height?: number): Matrix4; /** * Rotation + uniform scale about the model's own origin, in its local * east/north/up frame. * * Heading is clockwise from north; ENU is right-handed with +Z up, where a * POSITIVE rotation about Z turns north toward west. So a clockwise bearing is * a negative rotation — the sign here is the whole reason this is a named * function rather than an inline `rotateZ`. */ export declare function localPlacementMatrix(p: SitePlacement): Matrix4; /** * Companion to `localPlacementMatrix` + `coordinateOrigin` (the outline * overlay's own placement, see this file's header comment) — converts ONE * RAW local vertex (an `EdgeRow.path` point, in the model's own pre- * transform local frame — the exact same value `getPath` hands the * PathLayer) to real `[lng, lat]`, the SAME two steps deck.gl itself * applies to render that vertex on screen: `localPlacementMatrix`'s * `scale * Rz(-heading)` first, THEN interpret the result as an east/north * metre offset from `coordinateOrigin`. Needed for XY snapping (spec: * issue #34 Part A) against the BIM edges overlay specifically: its picked * rows carry this raw local form, not GeoJSON, so `snapping.ts`'s * resolver — which only understands `[lng, lat]` — cannot search them * directly without this. * * The metres-to-degrees step is the standard small-area linear * approximation (matches `getDistanceScales` inside * `@math.gl/web-mercator`, which is what ACTUALLY renders this exact * vertex on screen today — deliberately consistent with that real * projection rather than a more "correct" ellipsoidal model that would * silently disagree with it). */ export declare function localOffsetToLngLat(local: readonly [number, number], origin: readonly [number, number], heading: number, scale: number): [number, number]; /** * The `Tileset3D.modelMatrix` that moves a tileset anchored at `baked` onto `p`. * * `target · Rz · S · anchor⁻¹`: the inverse undoes the frame the converter * wrote, then the model is re-planted. With no `site-origin` the target IS the * anchor, so the inverse cancels it exactly and only the rotation and scale * survive — `site-heading` on its own spins the model where it stands. * * Both the pivot and the outline overlay's `coordinateOrigin` are the same * ground-level site point, so mesh and outlines rotate together. */ export declare function tilesetPlacementMatrix(baked: Matrix4, p: SitePlacement): Matrix4; export declare function tilesetBakedFrame(url: string, onReady: () => void): Matrix4 | null | undefined;