/** * Manages point cloud assets in the renderer. * * Supports two ingest modes: * - One-shot: `addAsset(asset)` for inline IFCx pointclouds. * - Streaming: `beginAsset(meta) → handle`, `appendChunk(handle, chunk)`, * `endAsset(handle)` for LAS/LAZ files arriving in chunks. * * The renderer owns the pipeline, per-asset GPU resources, and the per-frame * draw call. Designed to slot into the existing `Renderer.render()` so points * share the depth buffer and section-plane state with triangle meshes. */ import type { PointCloudAsset } from '@ifc-lite/geometry'; import { type PointCloudChunkInput, type PointCloudNode, type PointCloudNodeMeta } from './point-cloud-node.js'; import { type PointColorMode, type PointSizeMode } from './point-cloud-uniforms.js'; export interface ResolvedSectionPlane { normal: [number, number, number]; distance: number; enabled: boolean; flipped?: boolean; } export type { PointColorMode, PointSizeMode }; /** * How to size a splat on screen. * - `fixed-px` every splat is `pointSize` pixels wide * - `adaptive-world` splat covers `worldRadius` metres in source space, * projected each frame (closer → bigger) * - `attenuated` adaptive but clamped between 1 px and `pointSize` * so splats stay visible at the far plane and don't * blow up to half the screen when you nose into the * cloud — usually the best default for nav. */ export interface PointCloudDrawState { /** column-major view-projection matrix (16 floats) */ viewProj: Float32Array; /** Section plane already resolved by the main render path. */ sectionPlane?: ResolvedSectionPlane | null; /** Viewport size in pixels — needed by the splat shader to convert * pixel sizes into clip-space offsets. */ viewport?: { width: number; height: number; }; } export interface PointCloudRenderOptions { /** How to color points each frame. Defaults to 'rgb'. */ colorMode?: PointColorMode; /** RGBA in 0..1, used when colorMode === 'fixed'. */ fixedColor?: [number, number, number, number]; /** Splat size in pixels (mode='fixed-px'/'attenuated') or maximum size cap. */ pointSize?: number; /** Splat sizing strategy. Defaults to `attenuated`. */ sizeMode?: PointSizeMode; /** World-space splat radius in metres for adaptive / attenuated modes. * Defaults to 0.02 m which works well for typical 5–20 mm scan spacing. */ worldRadius?: number; /** Render splats as discs instead of squares. Defaults to true. */ roundShape?: boolean; /** * Per-LAS-class visibility bitmask covering the full 0..255 code * range (#1783). Bit `i % 32` of word `i / 32` set → class `i` is * visible. Pass up to 8 u32 words LSB-first (missing words default * to all-visible), or a plain `number` for the legacy 32-bit form * (classes 32..255 stay visible). Defaults to everything visible. * Only affects points carrying classifications; meshes ignore it. */ classMask?: number | ArrayLike; /** * Stride-cull factor for the splat shader: 1 = render every point, * 2 = every other, 4 = every fourth, etc. Used by the section-plane * preview path so dragging a slider over a 100M-point scan stays * responsive — UI flips this to e.g. 4 on drag start and back to 1 * on drag end. Default 1. */ previewStride?: number; /** * BIM↔scan deviation heatmap range. `centerOffset` shifts the * "white" point off zero (handy when a scan has a global offset * from the model); `halfRange` is the metres mapped to ±1 on the * blue→white→red ramp. Defaults to (0, 0.05) → ±5cm. * Only consulted when `colorMode === 'deviation'`. */ deviationRange?: { centerOffset: number; halfRange: number; }; } export interface PointCloudAssetHandle { readonly id: number; } /** * `PointCloudRenderOptions` with every field present and `classMask` * normalized to the 8-word uniform layout. */ export type ResolvedPointCloudRenderOptions = Omit, 'classMask'> & { classMask: Uint32Array; }; export declare class PointCloudRenderer { private device; private pipeline; private nodes; private nodeOwners; private nextHandleId; private uniformScratch; private uniformScratchU32; private options; constructor(device: GPUDevice, colorFormat: GPUTextureFormat, depthFormat: GPUTextureFormat, sampleCount: number); setOptions(opts: PointCloudRenderOptions): void; getOptions(): Readonly; /** * Replace every IFCx-owned asset with `assets`. Streamed assets are * untouched. Use this from the viewer's IFCx sync hook. */ setAssets(assets: ReadonlyArray): void; addAsset(asset: PointCloudAsset): PointCloudAssetHandle; /** Open an empty asset that chunks will be appended to. */ beginAsset(meta: PointCloudNodeMeta): PointCloudAssetHandle; appendChunk(handle: PointCloudAssetHandle, chunk: PointCloudChunkInput): void; /** Mark streaming complete. No-op for now — kept for symmetry. */ endAsset(handle: PointCloudAssetHandle): void; removeAsset(handle: PointCloudAssetHandle): void; /** * Reassign a streamed asset's `expressId` after upload — used by * `useIfcFederation` when the FederationRegistry hands out an * `idOffset` for the model. The shader reads expressId from a * per-asset uniform (flags.x), so this is just a metadata update; * the next frame writes the new value into the GPU uniform without * touching the per-vertex attributes. */ relabelAsset(handle: PointCloudAssetHandle, newExpressId: number): void; clear(): void; private clearOwner; hasAssets(): boolean; getNodeCount(): number; /** * Iterate every uploaded node. Exposed so the deviation compute * pass can reach each node's vertex + deviation buffers without * the renderer having to mirror its internal map. */ getInternalNodes(): Iterable; /** Total number of points currently uploaded across all assets. */ getPointCount(): number; getBounds(): { min: [number, number, number]; max: [number, number, number]; } | null; /** * Issue draw calls into an already-open render pass. The caller owns * the encoder/pass and is responsible for the depth attachment. */ draw(pass: GPURenderPassEncoder, state: PointCloudDrawState): void; /** * Resolve a packed objectId rgba8 sample back to the asset that owns it. * Returns null when the sample doesn't match any asset's expressId. */ resolvePick(expressId: number): { handle: PointCloudAssetHandle; meta: PointCloudNodeMeta; } | null; /** * Snapshot of nodes shaped for the picker — only the data the GPU * picking pass actually needs (expressId, modelIndex, chunk vertex * buffers + counts). Returns a fresh array; callers may iterate * freely without worrying about mutation during a pick. */ getPickNodes(): Array<{ expressId: number; modelIndex?: number; chunks: Array<{ vertexBuffer: GPUBuffer; pointCount: number; }>; }>; } //# sourceMappingURL=point-cloud-renderer.d.ts.map