/** * @ifc-lite/renderer - WebGPU renderer */ export { WebGPUDevice } from './device.js'; export { RenderPipeline } from './pipeline.js'; export { Camera } from './camera.js'; export type { ProjectionMode } from './camera-controls.js'; export { pickFitPolicy } from './camera-fit-policy.js'; export type { FitPolicy, FitPolicyKind, Bounds3, PickFitPolicyOptions } from './camera-fit-policy.js'; export { Scene } from './scene.js'; export { Picker } from './picker.js'; export { MathUtils } from './math.js'; export { SectionPlaneRenderer } from './section-plane.js'; export { Section2DOverlayRenderer } from './section-2d-overlay.js'; export { SymbolicFillPipeline, SymbolicTextPipeline, type SymbolicFillInput, type SymbolicTextInput, } from './symbolic-overlay-pipelines.js'; export { SymbolicTextAtlas } from './symbolic-text-atlas.js'; export { DEFAULT_CAP_STYLE, HATCH_PATTERN_IDS } from './section-cap-style.js'; export type { SectionCapStyle, HatchPatternId } from './section-cap-style.js'; export { planeBasis, nearestCardinalAxis } from './section-plane-basis.js'; export type { PlaneBasis, Vec3Tuple } from './section-plane-basis.js'; export type { Section2DOverlayOptions, Section2DOverlayCapStyle, CutPolygon2D, DrawingLine2D } from './section-2d-overlay.js'; export { Raycaster } from './raycaster.js'; export { SnapDetector, SnapType } from './snap-detector.js'; export { BVH } from './bvh.js'; export { FederationRegistry, federationRegistry } from './federation-registry.js'; export type { ModelRange, GlobalIdLookup } from './federation-registry.js'; export * from './types.js'; export { resolveEnvironment, deriveSkyGradient, packEnvironmentUniforms, ENVIRONMENT_UNIFORM_SIZE, } from './environment.js'; export type { LightingEnvironment, ResolvedEnvironment, SkyGradient, Vec3Color } from './environment.js'; export type { Ray, Vec3, Intersection } from './raycaster.js'; export type { SnapTarget, SnapOptions, EdgeLockInput, MagneticSnapResult } from './snap-detector.js'; export { PickingManager } from './picking-manager.js'; export type { PointPickProvider } from './picking-manager.js'; export { resolveContributionThresholdPx, projectedAabbRadiusPx } from './contribution-cull.js'; export type { ContributionCullOptions, CullCameraState } from './contribution-cull.js'; export { chunkCellKey, bucketBaseKeyFor, DEFAULT_CHUNK_CELL_SIZE } from './chunk-grid.js'; export type { SpatialChunkingConfig, ChunkAnchorSource } from './chunk-grid.js'; export { selectEvictions, MIN_EVICTION_AGE_FRAMES } from './residency.js'; export type { ResidencyShell, ColdGeometryProvider } from './residency.js'; export { simplifyIndicesByClustering, lodCellSizeForBounds, LOD_MIN_TRIANGLES, LOD_CELL_FRACTION } from './lod-simplify.js'; export { quantizeInterleaved, octEncode, octDecode, QUANT_STEP, MAX_QUANT_EXTENT, QUANT_BYTES_PER_VERTEX } from './quantize.js'; export type { QuantizedVertexData } from './quantize.js'; export { sumResidentGpuBytes } from './render-stats.js'; export type { FrameStats, ResidentGpuBytes } from './render-stats.js'; export { RaycastEngine } from './raycast-engine.js'; export { PointPicker, decodePickSample } from './point-picker.js'; export type { PointPickNode, DecodedPickSample } from './point-picker.js'; export type { PointPickSizing } from './picker.js'; export { PointCloudRenderer } from './pointcloud/point-cloud-renderer.js'; export type { PointCloudAssetHandle, PointCloudRenderOptions, PointColorMode, PointSizeMode, ResolvedSectionPlane as PointResolvedSectionPlane, } from './pointcloud/point-cloud-renderer.js'; export { PointRenderPipeline } from './pointcloud/point-pipeline.js'; export type { PointCloudChunkInput, PointCloudNode, PointCloudNodeMeta, } from './pointcloud/point-cloud-node.js'; import { RenderPipeline } from './pipeline.js'; import { Camera } from './camera.js'; import { Scene } from './scene.js'; import type { MeshData } from '@ifc-lite/geometry'; import type { RenderOptions, PickOptions, PickResult, Mesh } from './types.js'; import { type CutPolygon2D, type DrawingLine2D } from './section-2d-overlay.js'; import { type SymbolicFillInput, type SymbolicTextInput } from './symbolic-overlay-pipelines.js'; import { Raycaster, type Intersection } from './raycaster.js'; import { SnapDetector, type SnapTarget, type SnapOptions, type EdgeLockInput, type MagneticSnapResult } from './snap-detector.js'; import type { FrameStats } from './render-stats.js'; import type { PointCloudAsset } from '@ifc-lite/geometry'; /** * Main renderer class */ export declare class Renderer { private device; private pipeline; private camera; private scene; private picker; private canvas; private sectionPlaneRenderer; private section2DOverlayRenderer; private overlayLineColor; private symbolicFillPipeline; private symbolicTextPipeline; private postProcessor; private readonly interactionEffects; private edlPass; private skyPass; private edlOptions; private pointCloudRenderer; /** Set true at the end of `init()`; gates `whenReady()`. */ private ready; private readyWaiters; /** * Set once the GPU device is lost for a non-intentional reason (driver * reset / VRAM exhaustion — see `WebGPUDevice`). Every GPU resource is then * dead, so `render()` becomes a no-op (it would only spew validation errors) * until the host re-initialises the renderer. Consumers learn of this via * `onDeviceLost` and typically respond by reloading the model. */ private deviceLost; private deviceLostListeners; private deviationPipeline; /** * Cache of which mesh-set the BVH was built from. We rebuild on * `computeDeviations` only when the cached "fingerprint" misses, * so re-running deviation against the same model is a fast * dispatch — the BVH is multi-second on big BIMs and we don't * want to pay that on every slider drag. */ private deviationBvhFingerprint; private visualEnhancementState; private modelBounds; private pickingManager; private raycastEngine; private lastRenderErrorTime; private readonly RENDER_ERROR_THROTTLE_MS; private _renderCallCount; private _renderSkipCount; /** Snapshot of the last completed render() — see getFrameStats(). */ private _lastFrameStats; private _renderErrorCount; private _lastRenderError; private _renderRequested; private readonly _visibilityEpochs; private _visibilityVersion; private _partialBatchEpoch; private _lastColorOverrideGen; private _lastHadVisibilityFiltering; private _batchVisibilityEpoch; private _batchVisibilityCache; private _prevHydratedSelection; private _prevHydratedSelectionModelIndex; private _loggedSectionBounds; private readonly uniformScratch; private readonly uniformScratchU32; private _activePickSection; private _activePickClipBox; constructor(canvas: HTMLCanvasElement); /** * Initialize renderer */ init(): Promise; /** * Resolves once `init()` has finished and the GPU device + point-cloud * renderer are usable. Callers that may run before init completes — e.g. * dropping a point cloud immediately after the viewport mounts, before * the async WebGPU init resolves — should `await renderer.whenReady()` * before `beginPointCloudStream`, which otherwise throws * "Renderer not initialized". */ whenReady(): Promise; private markReady; /** * Subscribe to non-intentional GPU device loss (driver reset / VRAM * exhaustion — NOT an intentional `destroy()`). Fired at most once per * device. After it fires, `render()` is a no-op until the renderer is * re-initialised, so the typical response is to dispose this renderer and * reload the model. Returns an unsubscribe function. * * Camera and model state live on the CPU (JS) and survive device loss, so a * reload restores the model at its current orientation — the loss is a GPU * event, not a data loss. */ onDeviceLost(listener: (info: { message: string; reason: string; }) => void): () => void; /** True once the GPU device has been lost for a non-intentional reason. */ isDeviceLost(): boolean; private handleDeviceLost; /** * Replace all loaded point clouds with `assets`. * * Phase 0 entry point — single-chunk inline assets from IFCx * (`pcd::base64`, `points::array`, `points::base64`). Future phases * accept streaming sources via a different overload. */ setPointClouds(assets: ReadonlyArray): void; /** Append additional point clouds without clearing existing ones. */ addPointClouds(assets: ReadonlyArray): void; /** Total number of point cloud assets currently uploaded. */ getPointCloudAssetCount(): number; /** Total number of points across all point cloud assets. */ getPointCloudPointCount(): number; /** Drop all point cloud GPU resources. */ clearPointClouds(): void; /** * Streaming entry: open an empty asset that will receive chunks via * `appendPointCloudChunk`. Call `endPointCloudStream` when no more * chunks will arrive (currently a no-op but kept for symmetry). */ beginPointCloudStream(meta: { expressId: number; ifcType?: string; modelIndex?: number; }): import('./pointcloud/point-cloud-renderer.js').PointCloudAssetHandle; appendPointCloudChunk(handle: import('./pointcloud/point-cloud-renderer.js').PointCloudAssetHandle, chunk: import('./pointcloud/point-cloud-node.js').PointCloudChunkInput): void; endPointCloudStream(handle: import('./pointcloud/point-cloud-renderer.js').PointCloudAssetHandle): void; removePointCloudAsset(handle: import('./pointcloud/point-cloud-renderer.js').PointCloudAssetHandle): void; /** * Reassign a streamed point-cloud's expressId after upload. Use * this when the federation registry assigns a new model offset and * the renderer needs to emit the post-offset globalId in picking * outputs. The change takes effect on the next render — no GPU * buffer rewrite needed. */ relabelPointCloudAsset(handle: import('./pointcloud/point-cloud-renderer.js').PointCloudAssetHandle, newExpressId: number): void; /** * Compute model bounds from triangle meshes + remaining point clouds. * Called from removeAsset / clear paths so bounds shrink correctly. * Triangle meshes still drive the bounds when present (existing * Scene-driven path), so this only re-folds in the point cloud * extents over whatever the mesh path left. */ private recomputeModelBounds; /** Aggregate bounds across all batched + individual meshes. Returns * null if the scene has no mesh geometry. */ private computeMeshBounds; /** Apply rendering options (color mode, fixed override, point size). */ setPointCloudOptions(opts: import('./pointcloud/point-cloud-renderer.js').PointCloudRenderOptions): void; /** * Compute BIM ↔ scan deviation for every loaded point cloud asset. * * Walks every triangle in the scene (individual + batched meshes, * regardless of which IFC ingest path produced them — STEP, IFCx, * GLB, or federated combinations), builds a per-triangle BVH on * the GPU, then runs a closest-point compute pass per chunk that * writes signed distance into each chunk's deviation buffer. * * Returns metadata so the UI can populate a histogram + auto-range: * the per-asset point count, the suggested ±range from the 95th * percentile, and the bbox the BVH was built from. * * Idempotent: re-running with the same mesh set reuses the GPU * BVH (the BVH build dominates wall time on big BIMs). Pass * `forceRebuild: true` to invalidate. */ computeDeviations(opts?: { /** Clip range applied during compute. 0 → no clip. Default 1m. */ maxRange?: number; forceRebuild?: boolean; }): Promise<{ bvhTriangles: number; bvhNodes: number; chunksProcessed: number; pointsProcessed: number; bounds: { min: [number, number, number]; max: [number, number, number]; } | null; suggestedHalfRange: number; }>; /** * Aggregate every triangle source the scene exposes — individual * meshes (created on demand by picking / highlights) AND batched * meshes (the streaming geometry path's compact GPU buffers). * Both formats arrive as `MeshData`; the BVH builder doesn't care * which source they came from. */ private collectAllSceneMeshes; /** * Toggle Eye-Dome Lighting and tune its strength. * * EDL adds depth perception to point clouds (and meshes) via screen- * space depth gradient — silhouette pixels get a soft black halo. * Cheap: ~9 texture taps per pixel. Only runs when point clouds are * loaded. */ setEdlOptions(opts: { enabled?: boolean; strength?: number; radiusPx?: number; highQuality?: boolean; }): void; private expandModelBoundsForPointClouds; /** * Load geometry from GeometryResult or MeshData array * This is the main entry point for loading IFC geometry into the renderer * * @param geometry - Either a GeometryResult from geometry.process() or an array of MeshData */ loadGeometry(geometry: import('@ifc-lite/geometry').GeometryResult | import('@ifc-lite/geometry').MeshData[]): void; /** * Add multiple meshes to the scene (convenience method for streaming) * * @param meshes - Array of MeshData to add * @param isStreaming - If true, throttles batch rebuilding for better streaming performance */ addMeshes(meshes: import('@ifc-lite/geometry').MeshData[], isStreaming?: boolean): void; /** * Fit camera to view all loaded geometry */ fitToView(): void; /** * Add mesh to scene with per-mesh GPU resources for unique colors */ addMesh(mesh: Mesh): void; /** * Ensure all meshes have GPU resources (call after adding meshes if pipeline wasn't ready) */ ensureMeshResources(): void; /** * Get model bounds (used for section planes, fitToView, etc.) */ getModelBounds(): { min: { x: number; y: number; z: number; }; max: { x: number; y: number; z: number; }; } | null; /** * Set model bounds (used when computing bounds from batches) */ setModelBounds(bounds: { min: { x: number; y: number; z: number; }; max: { x: number; y: number; z: number; }; }): void; /** * Update model bounds from mesh data */ private updateModelBounds; /** * Create a GPU Mesh from MeshData (lazy creation for selection highlighting) * This is called on-demand when a mesh is selected, avoiding 2x buffer creation during streaming */ createMeshFromData(meshData: MeshData): void; /** * On a selection change, free the hydrated (pick/selection-highlight) * individual meshes whose entity is no longer selected. These duplicate * geometry already drawn by a batch; without this they accumulate in * scene.meshes (VRAM grows with selection history) and — for transparent * entities — re-draw every frame on top of their still-drawn batch copy * (double alpha-blend darkens glass). Currently-selected entities keep their * hydrated meshes so the highlight pass doesn't rebuild them each change. * No-op when the selection set is unchanged to avoid per-frame buffer churn. * Selection identity is the (modelIndex, expressId) PAIR: federated models * can share express ids, so re-selecting the same id in a different model * must still free the previous model's hydrated mesh (it would otherwise * stay resident and keep drawing unhighlighted). */ private syncHydratedSelectionMeshes; /** * Pop the frame's validation error scope, recording any captured validation * error into the device diagnostics (getDiagnostics().gpuErrors). The pop * itself REJECTS when the GPU device is lost while the scope is pending — * often the only evidence of the loss — so the rejection is logged * (throttled) and the context invalidated, never swallowed silently. Used * by every render() exit path that pushed a scope, so push/pop stay * balanced even on skipped or throwing frames. */ private drainErrorScope; private resolveVisualEnhancement; /** * Render frame */ /** Get diagnostic info for mobile debugging */ getDiagnostics(): { calls: number; skips: number; errors: number; lastError: string; batches: number; meshes: number; contextOk: boolean; gpuErrors: number; lastGpuError: string; camPos: string; camTgt: string; bounds: string; }; /** * Statistics of the last COMPLETED render() call (draw calls issued, * batches drawn / frustum-culled / contribution-culled), or null before * the first frame. Pair with `getScene().getResidentGpuBytes()` for the * load-complete telemetry snapshot (issue #1682 observability). */ getFrameStats(): FrameStats | null; /** * Probe the quantized pipeline variants and, when all exist, enable * 12-byte quantized batch vertices (issue #1682 phase 6). Returns * whether quantization is active — on failure (e.g. a fragile backend * rejecting pipeline creation) batches stay on the f32 path. */ enableQuantizedBatches(): Promise; render(options?: RenderOptions): void; /** * Pick object at screen coordinates * Respects visibility filtering so users can only select visible elements * Returns PickResult with expressId and modelIndex for multi-model support * * Note: x, y are CSS pixel coordinates relative to the canvas element. * These are scaled internally to match the actual canvas pixel dimensions. */ pick(x: number, y: number, options?: PickOptions): Promise; /** * Section plane + clip box from the last render(), so the picker discards the * same fragments and clipped-away geometry can't be selected. */ private activePickClip; /** * GPU-based rectangle pick. Drag-select returns the set of * `expressId`s touched by any pixel inside `[x0,y0]..[x1,y1]` * (CSS pixels, canvas-relative). Both meshes and point clouds * participate. * * See `PickingManager.pickRect` for the visibility-filter + * limitation notes. */ pickRect(x0: number, y0: number, x1: number, y1: number, options?: PickOptions): Promise>; /** * Raycast into the scene to get precise 3D intersection point * This is more accurate than pick() as it returns the exact surface point * * Note: x, y are CSS pixel coordinates relative to the canvas element. * These are scaled internally to match the actual canvas pixel dimensions. */ raycastScene(x: number, y: number, options?: PickOptions & { snapOptions?: Partial; }): { intersection: Intersection; snap?: SnapTarget; } | null; /** * Raycast with magnetic edge snapping behavior * This provides the "stick and slide along edges" experience * * Note: x, y are CSS pixel coordinates relative to the canvas element. * These are scaled internally to match the actual canvas pixel dimensions. */ raycastSceneMagnetic(x: number, y: number, currentEdgeLock: EdgeLockInput, options?: PickOptions & { snapOptions?: Partial; }): MagneticSnapResult & { intersection: Intersection | null; }; /** * Invalidate BVH cache (call when geometry changes) */ invalidateBVHCache(): void; /** * Get the raycaster instance (for advanced usage) */ getRaycaster(): Raycaster; /** * Get the snap detector instance (for advanced usage) */ getSnapDetector(): SnapDetector; /** * Clear all caches (call when geometry changes) */ clearCaches(): void; /** * Request a render on the next animation frame. * Safe to call many times per frame — only one render will happen. */ requestRender(): void; /** * Check whether a render has been requested without clearing the flag. * Used by the animation loop to test the dirty flag before committing * to render (e.g. when throttling may skip the frame). */ peekRenderRequest(): boolean; /** * Consume the render request flag. Returns true (and resets the flag) * if a render was requested since the last call. Used by the animation * loop to decide whether to render. */ consumeRenderRequest(): boolean; /** * Resize canvas */ resize(width: number, height: number): void; getCamera(): Camera; getScene(): Scene; /** * Upload 2D section drawing data for 3D overlay rendering. * * Cardinal-axis call site: pass `axis` + `position` percentage and the * upload computes the plane offset along the cardinal axis using the * model bounds (or `sectionRange` override). 2D points are then lifted * to 3D via the cardinal-axis swap. * * Custom-plane call site (issue #243): pass `customPlane = { origin, * tangent, bitangent }`. The 2D points are lifted via the explicit * basis, exactly inverting the projection `SectionCutter` applied when * generating the polygons. Without this the cap silhouette lands off * the actual cutting plane (the bug PR #581 hid by suppressing the * cap entirely for non-cardinal planes). */ uploadSection2DOverlay(polygons: CutPolygon2D[], lines: DrawingLine2D[], axis: 'down' | 'front' | 'side', position: number, // 0-100 percentage sectionRange?: { min?: number; max?: number; }, // Same storey-based range as section plane flipped?: boolean, customPlane?: { origin: [number, number, number]; tangent: [number, number, number]; bitangent: [number, number, number]; }): void; /** * Clear the 2D section overlay */ clearSection2DOverlay(): void; /** * Set the colour of the overlay lines (annotation / alignment / grid) and the * section-cut outline (RGBA, 0..1). Defaults to opaque black; theme it to keep * lines legible on a dark canvas. The matching label colour is per-text via * `SymbolicTextInput.color` on `uploadAnnotationTexts3D`. */ setOverlayLineColor(color: readonly [number, number, number, number]): void; /** * Upload pre-lifted 3D line-list vertices for the standalone annotation * overlay. Each segment is `[x1, y1, z1, x2, y2, z2]` in world space. * The overlay is drawn regardless of whether a section plane is active. * Pass an empty Float32Array to clear. */ uploadAnnotationLines3D(vertices: Float32Array): void; /** Walks a flat `[x,y,z,x,y,z,...]` vertex buffer and either initialises * or expands the cached `modelBounds` AABB. Used by the annotation * overlay upload paths so symbolic-only models can still be framed. * * The geometry pipeline pre-seeds a placeholder `[-100, 100]` cube on * every render when there are 0 meshes (so the section-plane slider * always has a workable range). For an annotation-only model that * fallback drowns out the much-smaller annotation cluster and a plain * "expand" would no-op. We detect the placeholder by its exact symmetric * signature and replace it with the actual annotation AABB instead. */ private expandModelBoundsWithFlatVertices; /** * Clear the standalone annotation line overlay. */ clearAnnotationLines3D(): void; /** * Upload IfcAlignment centerline segments as a flat [x,y,z,x,y,z,...] * line-list in world space. Rendered as thin lines (not a ribbon mesh) * to match IfcGrid / IfcAnnotation. Pass an empty Float32Array to clear. */ uploadAlignmentLines3D(vertices: Float32Array): void; /** Clear the alignment centerline overlay. */ clearAlignmentLines3D(): void; /** * Upload structural-grid (IfcGridAxis) segments as a flat [x,y,z,x,y,z,...] * line-list in world space (issue #967). Rendered as thin lines, mirroring * the alignment overlay. Pass an empty Float32Array to clear. * * Unlike alignment, grids do NOT expand model bounds: they're behind a * visibility toggle, so toggling them on must not reframe the camera (and * grid axes routinely extend past the model envelope). */ uploadGridLines3D(vertices: Float32Array): void; /** Clear the structural-grid overlay. */ clearGridLines3D(): void; /** * Show (or clear) the clash-overlap box: the wireframe AABB of a focused * clash, drawn in `color` so the overlap region reads as a distinct third * colour next to the two glowing clash elements (#1277). Pass `null` to * clear. `min`/`max` are world-space corners (clash works in world frame). */ setClashOverlapBox(box: { min: [number, number, number]; max: [number, number, number]; color: [number, number, number, number]; } | null): void; /** * Draw the focused clash's CONTACT geometry as 3D line segments — the real * shared-face polygon outlines / intersection lines, not the AABB box. * `vertices` is a flat line-list (x,y,z per endpoint, 2 endpoints per * segment) in world frame. Pass `null` to clear. Shares the clash-box line * buffer, so only one of this / setClashOverlapBox is shown at a time. */ setClashContactLines(lines: { vertices: Float32Array; color: [number, number, number, number]; } | null): void; /** * Upload filled IfcAnnotation regions for the symbolic overlay * (issue #653). Pass an empty array to clear. */ uploadAnnotationFills3D(fills: readonly SymbolicFillInput[]): void; /** * Upload IfcAnnotation text labels for the symbolic overlay * (issue #653). Pass an empty array to clear. */ uploadAnnotationTexts3D(texts: readonly SymbolicTextInput[]): void; /** * Check if 2D section overlay has geometry to render */ hasSection2DOverlay(): boolean; /** * Get render pipeline (for batching) */ getPipeline(): RenderPipeline | null; /** * Check if renderer is fully initialized and ready to use */ isReady(): boolean; /** * Get the GPU device (returns null if not initialized) */ getGPUDevice(): GPUDevice | null; /** * Capture a screenshot of the current view * Waits for GPU work to complete and captures exactly what's displayed * @returns PNG data URL or null if capture failed */ captureScreenshot(): Promise; /** * Destroy the renderer and release all GPU resources. * * Cleans up scene buffers, render pipeline textures, picking resources, * post-processing buffers, section-plane renderers, and snap caches. * After calling this method the renderer is no longer usable. * Safe to call multiple times (idempotent). */ destroy(): void; /** * Get the canvas element */ getCanvas(): HTMLCanvasElement; } //# sourceMappingURL=index.d.ts.map